C++ 经典排序分析
1、复杂度分析
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
2、快速排序
void quick(vector<int>& all, int left, int right) {
int jizhun, i, j;
if (left > right)return;//不合逻辑
jizhun = all[left];//找到基准值
i = left;//左右指针
j = right;
while (i != j) {
while (jizhun <= all[j] && i < j)j--;//从右至左找到小于基准值的
while (jizhun >= all[i] && i < j)i++;//从左至右找到大于基准值的
if (i < j)swap(all[i], all[j]);//找到后交换,使基准值左边的值小于他,右边的大于他。
}
all[left] = all[i];//把基准值放到适当的位置,符合上面的注释
all[i] = jizhun;
quick(all, left, i - 1);//左右分治
quick(all, i + 1, right);
}
int main() {
cout << "要排几个数字:";
int n;
cin >> n;
cout << "输入要排的数字:";
vector<int>all(n);
for (int i = 0; i < n; i++) {
cin >> all[i];
}
quick(all, 0, n - 1);
cout << "快排后的数字:";
for (auto& a : all)cout << a << " ";
return 0;
}
3、冒泡排序
冒泡排序在扫描过程中两两比较相邻记录,如果反序则交换,最终,最大记录就被“沉到”了序列的最后一个位置,第二遍扫描将第二大记录“沉到”了倒数第二个位置,重复上述操作,直到n-1 遍扫描后,整个序列就排好序了。
//冒泡
void Bub(vector<int>& all) {
if (all.size() <= 1)return;
for (int i = 0; i < all.size() - 1; i++) {
for (int j = 0; j < all.size() - 1 - i; j++) {
if (all[j] > all[j + 1])swap(all[j], all[j + 1]);
}
}
}
int main() {
cout << "要排几个数字:";
int n;
cin >> n;
cout << "输入要排的数字:";
vector<int>all(n);
for (int i = 0; i < n; i++) {
cin >> all[i];
}
Bub(all);
//quick(all, 0, n - 1);
cout << "排序后的数字:";
for (auto& a : all)cout << a << " ";
return 0;
}
4、选择排序
选择排序也是一种简单直观的排序算法。它的工作原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
//选择
void Sel(vector<int>& all) {
if (all.size() <= 1)return;
int i, j, minind;
for (i = 0; i < all.size() - 1; i++) {
minind = i;
for (j = i + 1; j < all.size(); j++) {
minind = all[j] < all[minind] ? j : minind;
}
swap(all[i], all[minind]);
}
}
int main() {
cout << "要排几个数字:";
int n;
cin >> n;
cout << "输入要排的数字:";
vector<int>all(n);
for (int i = 0; i < n; i++) {
cin >> all[i];
}
Sel(all);
//Bub(all);
//quick(all, 0, n - 1);
cout << "排序后的数字:";
for (auto& a : all)cout << a << " ";
return 0;
}
5、插入排序
直接插入排序(straight insertion sort),有时也简称为插入排序(insertion sort),是减治法的一种典型应用。其基本思想如下:
对于一个数组A[0,n]的排序问题,假设认为数组在A[0,n-1]排序的问题已经解决了。考虑A[n]的值,从右向左扫描有序数组A[0,n-1],直到第一个小于等于A[n]的元素,将A[n]插在这个元素的后面。
//插入
void Ins(vector<int>& all) {
if (all.size() <= 1)return;
for (int i = 1; i < all.size(); i++) {
for (int j = i; j > 0; j--) {
if (all[j] < all[j - 1])swap(all[j], all[j - 1]);
else break;
}
}
}
int main() {
cout << "要排几个数字:";
int n;
cin >> n;
cout << "输入要排的数字:";
vector<int>all(n);
for (int i = 0; i < n; i++) {
cin >> all[i];
}
Ins(all);
//Sel(all);
//Bub(all);
//quick(all, 0, n - 1);
cout << "排序后的数字:";
for (auto& a : all)cout << a << " ";
return 0;
6、归并排序
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
//归并
void MMar(vector<int>& all, int left, int mid, int right, vector<int>& temp) {
if (all.empty())return;
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
if (all[i] <= all[j]) {
temp[k++] = all[i++];
continue;
}
temp[k++] = all[j++];
}
while (i <= mid)temp[k++] = all[i++];
while (j <= right)temp[k++] = all[j++];
int t = 0;
int tt = left;
while (t < k)all[tt++] = temp[t++];
}
void MMst(vector<int>& all, int left, int right, vector<int>& temp) {
if (left < right) {
int mid = (left + right) / 2;
MMst(all, left, mid, temp);
MMst(all, mid + 1, right, temp);
MMar(all, left, mid, right, temp);
}
}
void Mst(vector<int>& all) {
if (all.size() <= 1)return;
vector<int>temp(all.size());
MMst(all, 0, all.size() - 1, temp);
}
int main() {
cout << "要排几个数字:";
int n;
cin >> n;
cout << "输入要排的数字:";
vector<int>all(n);
for (int i = 0; i < n; i++) {
cin >> all[i];
}
Mst(all);
//Ins(all);
//Sel(all);
//Bub(all);
//quick(all, 0, n - 1);
cout << "排序后的数字:";
for (auto& a : all)cout << a << " ";
return 0;
}
7、希尔排序
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
//希尔排序
void she(vector<int>& all) {
if (all.size() <= 1)return;
for (int i = all.size() / 2; i >= 1; i /= 2) {
for (int k = 0; k < i; k++) {
for (int ii = i + k; ii < all.size(); ii += i) {
for (int j = ii; j > k; j -= i) {
if (all[j] < all[j - i])swap(all[j], all[j - i]);
else break;
}
}
}
}
}
int main() {
cout << "要排几个数字:";
int n;
cin >> n;
cout << "输入要排的数字:";
vector<int>all(n);
for (int i = 0; i < n; i++) {
cin >> all[i];
}
she(all);
//Mst(all);
//Ins(all);
//Sel(all);
//Bub(all);
//quick(all, 0, n - 1);
cout << "排序后的数字:";
for (auto& a : all)cout << a << " ";
return 0;
}
8、堆排序
堆排序实际上是利用堆的性质来进行排序的,要知道堆排序的原理我们首先一定要知道什么是堆。
堆的定义:
堆实际上是一棵完全二叉树。
堆满足两个性质:
1、堆的每一个父节点都大于(或小于)其子节点;
2、堆的每个左子树和右子树也是一个堆。
堆的分类:
堆分为两类:
1、最大堆(大顶堆):堆的每个父节点都大于其孩子节点;
2、最小堆(小顶堆):堆的每个父节点都小于其孩子节点;
可以看出堆的第一个元素要么是最大值(大顶堆),要么是最小值(小顶堆),这样在排序的时候(假设共n个节点),直接将第一个元素和最后一个元素进行交换,然后从第一个元素开始进行向下调整至第n-1个元素。所以,如果需要升序,就建一个大堆,需要降序,就建一个小堆。
堆排序的步骤分为三步:
1、建堆(升序建大堆,降序建小堆);
2、交换数据;
3、向下调整。
堆的存储:
一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。
//堆排序大堆
void adH(vector<int>& all, int node, int len) {
int ind = node;
int child = 2 * ind + 1;
while (child < len) {
if (child + 1 < len && all[child] < all[child + 1]) {
child++;
}
if (all[ind] >= all[child])break;
swap(all[ind], all[child]);
ind = child;
child = 2 * ind + 1;
}
}
void makeH(vector<int>& all) {
for (int i = all.size() / 2; i >= 0; i--) {
adH(all, i, all.size());
}
}
void Hst(vector<int>& all) {
if (all.size() <= 1)return;
makeH(all);
for (int i = all.size() - 1; i >= 0; i--) {
swap(all[i], all[0]);
adH(all, 0, i);
}
}
int main() {
cout << "要排几个数字:";
int n;
cin >> n;
cout << "输入要排的数字:";
vector<int>all(n);
for (int i = 0; i < n; i++) {
cin >> all[i];
}
Hst(all);
//she(all);
//Mst(all);
//Ins(all);
//Sel(all);
//Bub(all);
//quick(all, 0, n - 1);
cout << "排序后的数字:";
for (auto& a : all)cout << a << " ";
return 0;
}