排序算法

冒泡排序
外循环–,内循环 ++(从后往前排好)
#include <stdio.h>
#include <stdbool.h>
#define DATA_SIZE 15
int bubble_sort(int *data, int len)
{
int temp;
bool swap_flag = true;
for (int i = len - 1; i > 0; i--) { // NOTE:NUN个数要比较NUM-1次,//后面先排好
swap_flag = false;
for (int j = 0; j < i; j++) { // NOTE:大的数放到后面,小的数放到前面,i之后的已经排好,i之前的需要排序
if (data[j] > data[j + 1]) {
temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
swap_flag = true;
}
}
if (swap_flag == false) //加入标志位,若已无需再排序,则直接结束排序
return 0;
}
return 0;
}
int main()
{
int data[DATA_SIZE] = {2, 5, 10, 34, 21, 6, 345, 12, 87, 1, 80, 54, 78, 32, 76};
bubble_sort(data, DATA_SIZE);
for (int i = 0; i < DATA_SIZE;i++)
printf("%d\t", data[i]);
return 0;
}
选择排序
双循环都是 ++(从前往后排好)
#include <stdio.h>
#define DATA_SIZE 15
int select_sort(int *data,int len)
{
int temp;
for (int i = 0; i < len - 1;i++){//前面先排好
for (int j = i + 1; j < len;j++){
if(data[j]<data[i]){
temp = data[j];
data[j] = data[i];
data[i] = temp;
}
}
}
return 0;
}
int main()
{
int data[DATA_SIZE] = {2, 5, 10, 34, 21, 6, 345, 12, 87, 1, 80, 54, 78, 32, 76};
select_sort(data, DATA_SIZE);
for (int i = 0; i < DATA_SIZE; i++)
printf("%d\t", data[i]);
return 0;
}
插入排序
外循环–,内循环 ++
#include <stdio.h>
#define DATA_SIZE 15
int insert_sort(int *data, int len)
{
int j;
int temp;
for (int i = 1; i < len; i++) { // 从左到右遍历,第一个位置不用动
temp = data[i];//不可省
for (j = i - 1; j >= 0; j--) { // 从前面已经排好的数组的最后一位开始向前比较
if (data[j] <= temp)
break;
else
data[j + 1] = data[j];//整体后移
}
data[j + 1] = temp;//插入
}
return 0;
}
int main()
{
int data[DATA_SIZE] = {2, 5, 10, 34, 21, 6, 345, 12, 87, 1, 80, 54, 78, 32, 76};
insert_sort(data, DATA_SIZE);
for (int i = 0; i < DATA_SIZE; i++)
printf("%d\t", data[i]);
return 0;
}
希尔排序
先分组,后排序
#include <stdio.h>
#define DATA_SIZE 15
int group_sort(int *data, int len, int pos, int step)
{
int i, j, temp;
for (i = pos + step; i < len; i = i + step) {
temp = data[i];
for (j = i - step; j >= 0; j=j-step) {
if (data[j] <= temp) {
break;
}
else {
data[j + step] = data[j];//逐步后移
}
}
data[j + step] = temp;
}
}
int shell_sort(int *data, int len)
{
int i;
int step;
for (step = len / 2; step > 0; step = step / 2) { // 分组,最后一次必定为1
for (i = 0; i < step; i++) { // 对每一组
group_sort(data, len, i, step);
}
}
return 0;
}
int main()
{
int data[DATA_SIZE] = {2, 5, 10, 34, 21, 6, 345, 12, 87, 1, 80, 54, 78, 32, 76};
shell_sort(data, DATA_SIZE);
for (int i = 0; i < DATA_SIZE; i++)
printf("%d\t", data[i]);
return 0;
}
归并排序
#define DATA_SIZE 15
int temp[DATA_SIZE] = {0};
void merge(int *data, int l, int mid, int r)
{
int i = l;
int j = mid + 1;
int k = l;
while (i <= mid && j <= r) {
if (data[i] <= data[j])
temp[k++] = data[i++];
else
temp[k++] = data[j++];
}
while(i<=mid)
temp[k++] = data[i++];
while (j <= r)
temp[k++] = data[j++];
for (int i = l; i <= r;i++){ //易错
data[i] = temp[i];
}
}
void merge_sort(int *data, int l, int r)
{
if(l>=r)
return;
int mid = (r + l) / 2;
merge_sort(data, l, mid);
merge_sort(data, mid + 1, r);
merge(data, l, mid, r);
}
int main(void)
{
int data[DATA_SIZE] = {2, 5, 10, 34, 21, 6, 345, 12, 87, 1, 80, 54, 78, 32, 76};
merge_sort(data, 0, DATA_SIZE - 1);
for (int i = 0; i < DATA_SIZE; i++)
printf("%d\t", data[i]);
return 0;
}
快速排序
- 取 data[0]作为基准
- 扫描数列,将比基准数小的元素全部放到它的左边,大于或等于基准数的元素全部放到它的右边,得到左右两个区间。
- 再对左右区间重复第二步,直到各区间少于两个元素。(右边找到并排序后换到左边,左右交替)
#include <stdio.h>
#define DATA_SIZE 15
int quick_sort(int *data, int len)
{
int temp, left, right;
temp = data[0];
left = 0;
right = len - 1;
int flag = 2;
if (len < 2)
return 0;
while (right > left) {
if (flag == 2) {
if (data[right] < temp) { // 把右边比轴小的移到轴左边
data[left] = data[right];
flag = 1; // 换边
}
else {
right--;
}
}
else {
if (data[left] > temp) { // 把左边比轴大的移到轴右边
data[right] = data[left];
flag = 2; // 换边
}
else {
left++;
}
}
}
data[left] = temp;
quick_sort(data, left); // 对左边操作
quick_sort(data + left + 1, len - left - 1); // 对右边操作
}
int main()
{
int data[DATA_SIZE] = {2, 5, 10, 34, 21, 6, 345, 12, 87, 1, 80, 54, 78, 32, 76};
quick_sort(data, DATA_SIZE);
for (int i = 0; i < DATA_SIZE; i++)
printf("%d\t", data[i]);
return 0;
}
改进
选取中间值作为参考
仅有开头一个等号
void func(int *data,int l,int r)
{
if(l>=r)
return;
int i =l-1; //不可省
int j = r+1; //不可省
int mid = (l+r)/2;
int val = data[mid]; //不可省
while(i<j){
do{ //不可省
i++;
}while(data[i]<val);
do{ //不可省
j--;
}while(data[j]>val);
if(i<j)
swap(data[i],data[j]);
}
func(data,l,j);
func(data,j+1,r);
}
堆排序
理解
堆排序就像整理一堆书:
- 建堆:先把所有书按照 “大的放上面” 的规则叠成一堆(构建最大堆)
- 取最大:每次从最上面拿走最大的书,放到书架的最后一格
- 重新整理:把剩下的书重新叠好,保持 “大的放上面” 的规则
- 重复:不断重复取最大和重新整理的步骤,直到所有书都按顺序排好
堆排序的特点是稳定可靠(相等元素不会乱序),时间复杂度始终是 O (n log n),但实际运行速度通常比快排稍慢。
重要结论
最后一个非叶子节点:
$$
i = \left( \frac{n}{2} \right)-1
$$
当前节点 i 的左孩子节点:
$$
left = 2*i+1
$$
当前节点 i 的右孩子节点:
$$
right = 2*i+2
$$
#include <iostream>
#include <vector>
// 调整堆,使以i为根的子树成为最大堆
void heapify(std::vector<int>& arr, int n, int i) {
int largest = i; // 初始化最大值为根节点
int left = 2 * i + 1; // 左子节点索引
int right = 2 * i + 2; // 右子节点索引
// 如果左子节点存在且大于根节点
if (left < n && arr[left] > arr[largest])
largest = left;
// 如果右子节点存在且大于当前最大值
if (right < n && arr[right] > arr[largest])
largest = right;
// 如果最大值不是根节点
if (largest != i) {
std::swap(arr[i], arr[largest]); // 交换根节点和最大值节点
// 递归调整受影响的子树
heapify(arr, n, largest);
}
}
// 堆排序主函数
void heapSort(std::vector<int>& arr) {
int n = arr.size();
// 1. 构建最大堆(从最后一个非叶子节点开始)
// 最后一个非叶子节点的索引 = (n/2) - 1
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 2. 逐个提取堆顶元素(最大值)并调整堆
for (int i = n - 1; i > 0; i--) {
// 将当前堆顶(最大值)与数组末尾元素交换
swap(arr[0], arr[i]);
// 对剩余元素重新堆化(堆大小减1)
heapify(arr, i, 0);
}
}
// 辅助函数:打印数组
void printArray(const std::vector<int>& arr) {
for (int num : arr)
std::cout << num << " ";
std::cout << std::endl;
}
// 测试
int main() {
std::vector<int> arr = {12, 11, 13, 5, 6, 7};
std::cout << "原始数组: ";
printArray(arr);
heapSort(arr);
std::cout << "排序后数组: ";
printArray(arr);
return 0;
}
堆排和快排分析
为什么堆排序是稳定的?
堆排序的稳定性源于其操作特性:
- 堆排序通过比较和交换操作来排序,当元素值相等时,不会改变它们的相对顺序
- 在堆的调整过程中,相等元素的父节点和子节点关系保持不变
- 每次提取堆顶元素后,只与末尾元素交换,不影响其他相等元素的相对位置
为什么快排是不稳定的?
快速排序的不稳定性主要由以下原因造成:
- 基准元素的选择和交换可能会破坏相等元素的相对顺序
- 分区过程中,相等元素可能被分到基准元素的两侧
- 例如:数组 [3, 2, 2],如果选择第一个 3 作为基准,经过一次分区后可能变成 [2, 2, 3],虽然结果正确,但如果原来的两个 2 有其他关联信息,它们的相对位置可能改变
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 宋振威的博客!
评论
