第十一章 排序
1.为什么要学排序
2 常见排序算法以及实现

插入排序
cpp
插⼊排序(Insertion Sort)类似于玩扑克牌插牌过程,每次将⼀个待排序的元素按照其关键字⼤⼩插⼊
到前⾯已排好序的序列中,按照该种⽅式将所有元素全部插⼊完成即可。

把大的数字后移
cpp
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
// 直接插入排序(数组下标从1开始)
void insert_sort()
{
// a[1]默认有序,从第2个元素开始处理
for (int i = 2; i <= n; i++)
{
int key = a[i];
int j = i - 1;
// 往前遍历,比key大的元素全部向后挪一位
while (j >= 1 && a[j] > key)
{
a[j + 1] = a[j];
j--;
}
// 空位放入当前待插入元素key
a[j + 1] = key;
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
insert_sort();
for (int i = 1; i <= n; i++)
{
cout << a[i] << " ";
}
return 0;
}

升序的时候只需要扫描一遍就可以了
选择排序
cpp
选择排序(Selection Sort)是⼀种特别直观的排序算法。每次找出未排序序列中最⼩的元素,然后放进
有序序列的后⾯。

cpp
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
// 选择排序(数组下标从1开始,升序排列)
void selection_sort()
{
// i代表已排序区间末尾的下一个位置,一共需要确定 n-1 个元素
for(int i = 1; i < n; i++)
{
int pos = i; // 记录最小值下标,初始假定当前i位置最小
// 在 [i, n] 未排序区间里寻找最小值下标
for(int j = i + 1; j <= n; j++)
{
if(a[j] < a[pos])
{
pos = j;
}
}
// 将找到的最小值交换到当前i的位置
swap(a[i], a[pos]);
}
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
}
selection_sort();
for(int i = 1; i <= n; i++)
{
cout << a[i] << " ";
}
return 0;
}



冒泡排序


两两比较 不断比较

这个是没有优化的
而优化的是当在for循环的时候不进行交换 那么就交换完成了
cpp
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
// 带有序标记优化版冒泡排序(升序,数组下标从1开始)
void bubble_sort()
{
// i:每轮冒泡后,末尾i位置已有序,无序区间为 [1, i]
for (int i = n; i > 1; i--)
{
bool flag = false; // 标记本轮是否发生交换
// 相邻元素两两比较,大值向后冒泡
for (int j = 1; j < i; j++)
{
if (a[j] > a[j + 1])
{
swap(a[j], a[j + 1]);
flag = true;
}
}
// 本轮没有任何交换,说明数组整体已经有序,直接提前退出
if (flag == false)
return;
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
bubble_sort();
for (int i = 1; i <= n; i++)
{
cout << a[i] << " ";
}
return 0;
}

堆排序

重新创建一个堆 然后将数据传进去 这个堆是不传统的
传统的是在原有得到数据上进行操作堆



cpp
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
// 堆下沉操作:维护以parent为根,长度为len的大根堆
void down(int parent, int len)
{
int child = parent * 2;
while(child <= len)
{
// 选出左右子节点中更大的那个
if(child + 1 <= len && a[child + 1] > a[child])
child++;
// 父节点大于等于最大子节点,无需下沉直接退出
if(a[parent] >= a[child])
return;
// 父子交换
swap(a[parent], a[child]);
// 更新父、子节点下标,继续向下调整
parent = child;
child = parent * 2;
}
}
void heap_sort()
{
// 1. 自底向上建大根堆
for(int i = n / 2; i >= 1; i--)
{
down(i, n);
}
// 2. 堆顶最大值交换到数组末尾,再调整剩余堆
for(int i = n; i > 1; i--)
{
swap(a[1], a[i]);
down(1, i - 1);
}
}
int main()
{
cin >> n;
// 数组从下标1开始存储数据
for(int i = 1; i <= n; i++)
cin >> a[i];
heap_sort();
// 输出排序后的数组
for(int i = 1; i <= n; i++)
cout << a[i] << " ";
return 0;
}




快速排序




cpp
#include <iostream>
#include <ctime>
using namespace std;
int main()
{
srand(time(0)); // 种下一个随机数种子
for (int i = 1; i <= 10; i++)
{
cout << rand() << endl; // 每次生成一个随机值
}
return 0;
}



cpp
#include <iostream>
#include <ctime>
#include <algorithm> // swap函数头文件,补上更规范
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
// 随机选取 [left, right] 范围内的基准值
int get_random(int left, int right)
{
int pos = rand() % (right - left + 1) + left;
return a[pos];
}
// 荷兰国旗快排:小于p | 等于p | 大于p 三段划分
void quick_sort(int left, int right)
{
if (left >= right)
return;
int p = get_random(left, right);
int l = left - 1;
int i = left;
int r = right + 1;
while (i < r)
{
if (a[i] < p)
{
swap(a[++l], a[i++]);
}
else if (a[i] == p)
{
i++;
}
else // a[i] > p
{
swap(a[--r], a[i]);
}
}
// [left, l] 小于基准,[l+1, r-1]等于基准,[r, right]大于基准
quick_sort(left, l);
quick_sort(r, right);
}
int main()
{
srand(time(0)); // 设置随机种子
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
quick_sort(1, n);
for (int i = 1; i <= n; i++)
cout << a[i] << " ";
return 0;
}



归并排序



cpp
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N];
int tmp[N]; // 归并合并阶段使用的临时辅助数组
void merge_sort(int left, int right)
{
// 区间只有一个元素,天然有序,直接返回
if (left >= right)
return;
// 中点拆分区间
int mid = (left + right) >> 1; // 等价于 (left + right) / 2
// 递归分别排序左半段、右半段
merge_sort(left, mid);
merge_sort(mid + 1, right);
// 合并两个已经有序的子数组
int cur1 = left; // 左区间遍历指针
int cur2 = mid + 1; // 右区间遍历指针
int i = left; // tmp数组写入指针
// 双指针合并,取较小值放入tmp
while (cur1 <= mid && cur2 <= right)
{
if (a[cur1] <= a[cur2])
tmp[i++] = a[cur1++];
else
tmp[i++] = a[cur2++];
}
// 左区间剩余元素全部拷贝
while (cur1 <= mid)
tmp[i++] = a[cur1++];
// 右区间剩余元素全部拷贝
while (cur2 <= right)
tmp[i++] = a[cur2++];
// 把临时数组有序数据写回原数组a
for (int j = left; j <= right; j++)
a[j] = tmp[j];
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
merge_sort(1, n);
for (int i = 1; i <= n; i++)
cout << a[i] << " ";
return 0;
}





