一、概念

1. 排序的定义

一般定义:将一组无序的数据元素调整为有序的数据元素。
数学定义:假设含n个数据元素的序列为R1,R2,...,Rn{R_1,R_2,...,R_n}R1​,R2​,...,Rn​,其相应的关键字序列为K1,K2,...,Kn{K_1,K_2,...,K_n}K1​,K2​,...,Kn​,这些关键字相互之间可以进行比较,即在它们之间存在这样的关系:Kp1≤Kp2≤...≤KpnK_{p1}\le K_{p2}\le...\le K_{pn}Kp1​≤Kp2​≤...≤Kpn​,按此固有关系将上式记录序列重新排序为:{Rp1,Rp2,...,Rpn}\{R_{p1}, R_{p2}, ... ,R_{pn}\}{Rp1​,Rp2​,...,Rpn​}的操作称为排序。

2. 排序的前提条件

首先,要排序的数据元素必须类型相同。
其次,数据元素之间是可以比较的,即只要给定两个元素,就可以说出谁“大”谁“小”,当然,很多情况下“大”与“小”是人为定义的。

例:
整数或小数比较:可以直接比较数值大小。
字母比较:字母在前面的被认为更小,字母在后面的被认为更大。如’a’比’z’小。
字符串比较:按字典序比较,字典序在前的被认为更小,字典序在后面的被认为更大。
数对比较:数对的第一个数字更大的数对更大,数对第一个数字相同时,第二个数字更大的数对更大。

第三,要明确排成升序还是降序。
严格意义下,有序序列的顺序有以下4种:

  • 升序序列:后面的数据元素大于前面的数据元素。
  • 不降序列:后面的数据元素大于等于前面的数据元素。
  • 不升序列:后面的数据元素小于等于前面的数据元素。
  • 降序序列:后面的数据元素小于前面的数据元素。

以下讨论时,使用非严格叙述,即用升序表示升序序列及不降序列,用降序表示不升序列及降序序列。

3. 内部排序和外部排序:

  1. 内部排序:整个排序过程不需要访问外存便能完成。
  2. 外部排序:待排序的数据元素数量很大,整个序列的排序过程需要借助外存才能完成。

本文只介绍内部排序。

4. 排序中的关键操作

  1. 比较:任意两个数据元素通过比较确定先后顺序。
    根据元素间比较规则写出元素比较的表达式或函数,表达式的值必须是布尔值,表示第一个元素是否比第二个元素小。
  2. 交换:两个数据元素之间数据值发生了交换。
    常用到STL中的swap函数,可以实现任意类型变量值的交换。其实现为:
template<class T>
void Swap(T &a, T &b)//STL中的函数为swap(),这里为了加以区分,写为Swap()
{T t = a;a = b;b = t;
}

5. 排序算法的稳定性

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,那么这个排序算法就是稳定的。
即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

5. 对排序的评价

  1. 时间复杂度:关键性能差异体现在比较和交换的次数
  2. 辅助空间复杂度:为完成排序操作需要的额外的存储空间的大小
    本身存储数据的存储空间不算在内,只考虑由于选择这一排序算法带来的额外空间开销。

6. 排序图示及动画

这个已经有很多人做过了,本文不再给出相关图示及动画。排序动画可以在这个网站看:
visualgo.net排序动画

二、排序算法

n为待排序的元素个数。
本文以该题为例题:洛谷 P1177 【模板】快速排序
复杂度为O(n2)O(n^2)O(n2)的排序算法只能通过该题的一个测试点。
计数排序无法完成该题。
希尔排序、基数排序以及多数复杂度为O(nlogn)O(nlogn)O(nlogn)的排序算法可以完全通过该题所有测试点。
该题几个测试点测试了极端情况,比如序列一开始就是有序的,或都是同一个值。一些排序算法在极端情况下复杂度会退化为O(n2)O(n^2)O(n2),因而可能有几个点过不了。
本文只给出下标从1开始的数组的排序算法代码。下标从0开始的数组的排序算法代码与之类似,不再赘述。
本文只给出升序排序算法代码,降序排序一般只需要将比较符号修改一下即可实现,不再赘述。

1. 直接选择排序

  • 基本思想:每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在待排序的数列的最前面,直到全部待排序的数据元素排完。
    第1次在下标1~n中选择最小值,与下标1的元素进行交换。
    第2次在下标2~n中选择最小值,与下标2的元素进行交换。
    第i次在下标i~n中选择最小值,与下标i的元素进行交换。
    第n-1次,在下标n-1~n中选择最小值,与下标n-1的元素进行交换。
  • 复杂度:时间复杂度:O(n2)O(n^2)O(n2)    额外空间复杂度:O(1)O(1)O(1)
  • 稳定性:不稳定
  • 示例:
    粗体数字为当前要替换的位置,斜体数字为已经确定的有序序列

8 3 2 6 4 从8到末尾的范围内,选择最小值2与8交换
2 3 8 6 4 从3到末尾的范围内,选择最小值3与3交换(实际不变)
2 3 8 6 4 从8到末尾的范围内,选择最小值4与8交换
2 3 4 6 8 从6到末尾的范围内,选择最小值6与6交换(不变)
2 3 4 6 8 所有数字都已确定位置

  • 代码:
    写法1:找出最小值而后交换
#include <bits/stdc++.h>
using namespace std;
int main()
{int a[100005], n, mi;cin >> n;for(int i = 1; i <= n; ++i)//输入数据 cin >> a[i];for(int i = 1; i <= n-1; ++i)//直接选择排序 升序排序 {mi = i;//mi:从i到n的最小值的下标 for(int j = i+1; j <= n; ++j)if(a[j] < a[mi])//如为 > 即可实现降序排序 mi = j;swap(a[i], a[mi]);}for(int i = 1; i <= n; ++i)//输出数据 cout << a[i] << ' ';return 0;
}

写法2:不断和第i位置交换

#include <bits/stdc++.h>
using namespace std;
int main()
{int a[100005], n;cin >> n;for(int i = 1; i <= n; ++i)//输入数据 cin >> a[i];for(int i = 1; i <= n-1; ++i)//直接选择排序 升序排序 for(int j = i+1; j <= n; ++j)if(a[j] < a[i])//如为 > 即可实现降序排序swap(a[i], a[j]);for(int i = 1; i <= n; ++i)//输出数据 cout << a[i] << ' ';return 0;
}

2. 冒泡排序

  • 基本思想:重复地访问要排序的元素序列,依次比较两个相邻的元素,如果两元素的顺序与预期顺序不符,就将二者交换。
    每趟冒泡后,最大(最小)的元素会经由交换“浮”到序列右端(或左端)。多次冒泡后,即可完成排序。
  • 复杂度:时间复杂度:O(n2)O(n^2)O(n2)    额外空间复杂度:O(1)O(1)O(1)
  • 稳定性:稳定
  • 示例
    粗体数字为要比较的两个数字,斜体数字为已经确定位置的数字

第1趟冒泡
8 3 2 6 4
3 8 2 6 4
3 2 8 6 4
3 2 6 8 4
3 2 6 4 8
第2趟冒泡
3 2 6 4 8
2 3 6 4 8
2 3 6 4 8
2 3 4 6 8
第3趟冒泡
2 3 4 6 8
2 3 4 6 8
2 3 4 6 8
第4趟冒泡
2 3 4 6 8
2 3 4 6 8
最后,第一个数字的位置自然被确定
2 3 4 6 8

  • 代码:
    写法1:易记版本:i从1到n-1,j从1到n-i
#include <bits/stdc++.h>
using namespace std;
int main()
{int n, a[100005];cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];for(int i = 1; i <= n-1; ++i)//i:需要冒泡n-1次。for(int j = 1; j <= n-i; ++j)//j: 要交换的数对中第一个数的位置。最后一次比较的数对的第一个数字位于n-i位置。if(a[j] > a[j+1])swap(a[j], a[j+1]);for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0;
}

写法2:符合概念的写法

#include <bits/stdc++.h>
using namespace std;
int main()
{int n, a[100005];cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];for(int i = n; i >= 2; --i)//i:此次冒泡,最大值最后落在的位置。for(int j = 1; j <= i - 1; ++j)//j: 要交换的数对中第一个数的位置if(a[j] > a[j + 1])swap(a[j], a[j + 1]);for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0;
}

写法3:将最小值冒泡到前面的写法

#include <bits/stdc++.h>
using namespace std;
int main()
{int n, a[100005];cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];for(int i = 1; i <= n - 1; ++i)//i:从后向前冒泡,最小值所在位置 for(int j = n; j >= i + 1; --j)//j:比较数对第二个数的位置 if(a[j] < a[j - 1])swap(a[j], a[j - 1]);     for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0;
}

3. 插入排序

  • 基本思想:插入排序是指在待排序的元素中,假设前面n-1个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,使得插入后这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程。
  • 复杂度:时间复杂度:O(n2)O(n^2)O(n2)    额外空间复杂度:O(1)O(1)O(1)
  • 稳定性:稳定
  • 示例
    粗体数字为当前正在插入的数字,斜体数字为已经构造好的有序序列
    每次将正在插入的数字和其前面的数字比较

第1个数字自然构成一个有序序列
8 3 2 6 4
插入第2个数字
8 3 2 6 4
3 8 2 6 4
插入第3个数字
3 8 2 6 4
3 2 8 6 4
2 3 8 6 4
插入第4个数字
2 3 8 6 4
2 3 6 8 4
插入第5个数字
2 3 6 8 4
2 3 6 4 8
2 3 4 6 8
2 3 4 6 8

  • 代码
    写法1:不断交换元素
#include <bits/stdc++.h>
using namespace std;
int main()
{int n, a[100005];cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];for(int i = 2; i <= n; ++i)//将第i个数字插入有序序列{for(int j = i; j > 1; j--)//j指向待插入数字{if(a[j] < a[j - 1])swap(a[j], a[j - 1]);elsebreak;}}for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0;
}

写法2:用一个变量保存要插入的值

#include <bits/stdc++.h>
using namespace std;
int main()
{int n, a[100005];cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];for(int i = 2; i <= n; ++i)//将第i个数字插入有序序列{int j, num = a[i];//num:待插入数字 for(j = i; j > 1; j--)//j指向待插入数字的位置{if(num < a[j - 1])a[j] = a[j - 1];elsebreak;}a[j] = num;} for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0;
}

写法3:可以一边输入一边做插入排序(常用)

#include <bits/stdc++.h>
using namespace std;
int main()
{int n, a[100005];cin >> n;for(int i = 1; i <= n; ++i){cin >> a[i];for(int j = i; j > 1; --j){if(a[j] < a[j-1])swap(a[j], a[j-1]);elsebreak;}}for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0;
}

4. 希尔排序

  • 基本思想:希尔排序是把数据元素按下标的一定增量分组,对每组使用直接插入排序算法排序。随着增量逐渐减少,每组包含的元素越来越多,当增量减至 1 时,整个数组就已经完成排序。
  • 复杂度:时间复杂度:O(n1.3)∼O(n2)O(n^{1.3}) \sim O(n^2)O(n1.3)∼O(n2)    额外空间复杂度:O(1)O(1)O(1)
  • 稳定性:不稳定
  • 示例:

初始增量n/2,为3
8 3 2 6 4 1  8与6一组,3与4一组,2与1一组,分别做插入排序,结果为:6 3 1 8 4 2
增量除以2,为1
6 3 1 8 4 2    做插入排序,结果为:1 2 3 4 6 8

  • 代码:
#include <bits/stdc++.h>
using namespace std;
int main()
{int n, a[100005];cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];for(int g = n/2; g > 0; g /= 2)//g:增量{for(int i = g+1; i <= n; ++i){for(int j = i; j > g; j-=g){if(a[j] < a[j-g])swap(a[j], a[j-g]);elsebreak;}}} for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0;
}

5. 计数排序

  • 基本概念:
    散列思想:通过散列函数把数据值对应到一个数组下标
    桶排序:将数据通过散列函数分配到有限数量的有序的桶里,每个桶内分别排序,最后将各个桶中的数据合并。
    计数排序:是一种特殊的桶排序。桶的个数是可能出现的数据种类数。
    计数数组:数组下标为数据值,数组的值表示数据个数。即a[i]表示数字i出现的个数。
    计数排序只适用于对范围有限的整数进行排序。
  • 复杂度:
    数据个数为n,数据范围为k
    时间复杂度:O(n+k)O(n+k)O(n+k)
    额外空间复杂度:O(k)O(k)O(k)
  • 稳定性:稳定
  • 示例:

待排序数据:1, 5, 3, 1, 3, 2
输入数据并统计后,计数数组c为

下标 1 2 3 4 5
2 1 2 0 1

遍历计数数组,将i输出c[i]次,为:1 1 2 3 3 5

  • 代码
    用计数排序做不了P1177,这里用计数排序完成的问题为:
    给定n个1~1000范围内的整数(n≤104n \le 10^4n≤104),输出其升序序列。
#include <bits/stdc++.h>
using namespace std;
int main()
{int n, a, c[1005] = {};cin >> n;for(int i = 1; i <= n; ++i){cin >> a;c[a]++;}for(int i = 1; i <= 1000; ++i){for(int j = 1; j <= c[i]; ++j)cout << i << ' ';}return 0;
}

6. 基数排序

  • 基本概念:基数排序是一种整数排序算法,其原理是将整数按每个位数分别比较。相当于多趟桶排序,适合位数有限的整数排序。
  • 复杂度:
    d是数字位数,n是数字个数,r是基数(进制)
    时间复杂度:O(d(n+r))O(d(n+r))O(d(n+r))
    额外空间复杂度:O(r+n)O(r+n)O(r+n)
  • 稳定性:稳定
  • 示例:

待排序数字:53, 542, 3, 63, 14, 214
第一趟按个位排序后:542, 53, 3, 63, 14, 214
第二趟按十位排序后:3, 14, 214, 542, 53, 63
第三趟按百位排序后:3, 14, 53, 63, 214, 542

  • 代码:
#include <bits/stdc++.h>
using namespace std;
#define N 100005
int countBit(int x)//输出整数x的位数
{return floor(log10(x)) + 1;
}
int main()
{int a[N], temp[N], n, mxBit = 0, buk[15];//mxBit:数字中的最大位数 cin >> n;for(int i = 1; i <= n; ++i){cin >> a[i];mxBit = max(mxBit, countBit(a[i]));}for(int b = 1, rad = 1; b <= mxBit; ++b, rad *= 10)//每趟针对第b位进行桶排序,位权rad {memset(buk, 0, sizeof(buk));//桶清零 for(int i = 1; i <= n; ++i)//按第b位加入桶中计数 buk[i]表示第b位为i的数字数量 buk[a[i]/rad%10]++;for(int i = 1; i <= 9; ++i)//buk[i]的概念变为第b位为i的数字的最大下标 buk[i] += buk[i-1];for(int i = n; i >= 1; --i)//临时数组temp的buk[k]位置添加数字a[i],buk[k]作为下一次添加数字的下标,应该减1。由大向小遍历。 {int k = a[i]/rad%10; temp[buk[k]--] = a[i];}for(int i = 1; i <= n; ++i)a[i] = temp[i]; }for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0;
}

7. 归并排序

  • 基本概念:
  1. 合并有序序列
    给定两个长为a、b的有序序列,合并成长为a+b的有序序列。
  2. 归并排序对序列的元素进行逐层折半分组,然后从最小分组开始合并有序序列,逐层进行,最后得到整个序列的排序结果。
  • 复杂度:
    时间复杂度:O(nlogn)O(nlogn)O(nlogn)
    空间复杂度:O(n)O(n)O(n)
  • 稳定性:稳定
  • 示例:

待排序数字序列 1 5 6 3 2 7 9 8
先进行折半分组,对于分组后的每一组,再进行折半分组,直到每组只有1个元素
1 5 6 3 | 2 7 9 8
1 5 | 6 3 | 2 7 | 9 8
1 | 5 | 6 | 3 | 2 | 7 | 9 | 8
依次对于每次分开的两组合并有序序列
1 5 | 3 6 | 2 7 | 8 9
1 3 5 6 | 2 7 8 9
1 2 3 5 6 7 8 9

  • 代码
#include<bits/stdc++.h>
using namespace std;
#define N 100005
int a[N], t[N];//t:临时数组
void mergeSort(int l, int r)//a数组下标l到r进行归并排序
{if(l >= r)return;int mid = (l + r)/2;mergeSort(l, mid);mergeSort(mid+1, r);int ti = l, li = l, ri = mid+1;//li第一个段数组中的下标 ri第二段数组中的下标 ti临时数组中的下标while(li <= mid && ri <= r){if(a[li] < a[ri])t[ti++] = a[li++];elset[ti++] = a[ri++];}while(li <= mid)//如果其中一段数组都填入t了,那么将另一段数组剩下的所有数字都填入tt[ti++] = a[li++];while(ri <= r)t[ti++] = a[ri++];for(int i = l; i <= r; ++i) a[i] = t[i];
}
int main()
{int n;cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];mergeSort(1, n);for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0;
}

8. 快速排序

  • 基本概念:
    通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小。分别对这两部分记录继续进行快速排序,以达到整个序列有序。

  • 复杂度:
    时间复杂度:O(nlogn)O(nlogn)O(nlogn)
    额外空间复杂度:O(1)O(1)O(1)

  • 稳定性:不稳定

  • 代码

#include<bits/stdc++.h>
using namespace std;
#define N 100005
int a[N];
void quickSort(int l, int r)
{  if(l >= r)return;int i = l, j = r;int mid = a[(l+r)/2];//将当前序列在中间位置的数定义为分隔数while(i <= j)//注意这里不能少了等号{while(a[i] < mid) i++;  //在左半部分寻找大于等于中间数的数while(a[j] > mid)j--;    //在右半部分寻找小于等于中间数的数if(i <= j) //若找到一组与排序目标不一致的数对{                               swap(a[i], a[j]);//则交换它们 i++;j--;}}quickSort(l, j);//递归搜索左右区间quickSort(i, r);
}
int main()
{int n;cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];quickSort(1, n);for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0;
}

9. 堆排序

  • 基本概念:堆是一种基于二叉树的数据结构,借助堆可以在O(logn)O(logn)O(logn)时间复杂度下获取n个元素中的最值。
  • 复杂度:
    时间复杂度:O(nlogn)O(nlogn)O(nlogn)
    额外空间复杂度:O(1)O(1)O(1)
  • 稳定性:不稳定

实现堆排序有两种方法
方法1:似于选择排序,构造小顶堆,每次取出所有元素中的最小值,并删除堆顶。循环n次即可获得有序序列。

#include <bits/stdc++.h>
using namespace std;
#define N 100005
int heap[N], n;
//b是否比a高级。堆顶是最高级的元素
bool isPrior(int a, int b)
{return a > b;//a > b:小顶堆, a < b:大顶堆
}
//第i结点上移
void shiftUp(int i)
{if(i == 1)//p是根结点 return;if(isPrior(heap[i/2], heap[i])){swap(heap[i], heap[i/2]);shiftUp(i/2);}
}
//第i结点下沉
void shiftDown(int i)
{if(i > n/2)//如果i是叶子结点return; int sel;//选择交换的结点位置 if(2*i+1 <= n && isPrior(heap[2*i],heap[2*i+1]))//有右孩子且右孩子值更大 sel = 2*i + 1;else//没有右孩子或左孩子值更大 sel = 2*i; if(isPrior(heap[i],heap[sel])){swap(heap[sel], heap[i]);shiftDown(sel); }
}
//建堆
void buildHeap()
{for(int i = n/2; i >= 1; --i)shiftDown(i);
}
//插入元素
void insert(int val)
{heap[++n] = val;shiftUp(n);
}
//删除元素
void del()
{swap(heap[1], heap[n]);n--;shiftDown(1);
}
//获取堆顶
int top()
{return heap[1];
}
int main()
{cin >> n;for(int i = 1; i <= n; ++i)cin >> heap[i];buildHeap();while(n > 0)//堆不空 {cout << top() << ' ';del();}return 0;
}

方法2:构造大顶堆

  1. 首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
  2. 将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
  3. 将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
#include <bits/stdc++.h>
using namespace std;
#define N 100005
int heap[N], n;
//b是否比a高级。堆顶是最高级的元素
bool isPrior(int a, int b)
{return a < b;//a > b:小顶堆, a < b:大顶堆
}
//第i结点上移
void shiftUp(int i)
{if(i == 1)//p是根结点 return;if(isPrior(heap[i/2], heap[i])){swap(heap[i], heap[i/2]);shiftUp(i/2);}
}
//第i结点下沉
void shiftDown(int i)
{if(i > n/2)//如果i是叶子结点return; int sel;//选择交换的结点位置 if(2*i+1 <= n && isPrior(heap[2*i],heap[2*i+1]))//有右孩子且右孩子值更大 sel = 2*i + 1;else//没有右孩子或左孩子值更大 sel = 2*i; if(isPrior(heap[i],heap[sel])){swap(heap[sel], heap[i]);shiftDown(sel); }
}
//建堆
void buildHeap()
{for(int i = n/2; i >= 1; --i)shiftDown(i);
}
//插入元素
void insert(int val)
{heap[++n] = val;shiftUp(n);
}
//删除元素
void del()
{swap(heap[1], heap[n]);n--;shiftDown(1);
}
//获取堆顶
int top()
{return heap[1];
}
//堆排序
void heapSort()
{int tot = n;for(int i = 1; i <= tot - 1; ++i)del();for(int i = 1; i <= tot; ++i)cout << heap[i] << ' ';
}
int main()
{cin >> n;for(int i = 1; i <= n; ++i)cin >> heap[i];buildHeap();heapSort();return 0;
}

10. 二叉排序树排序

  • 基本概念
    二叉排序树或者是一颗空树,或者是具有如下性质的二叉树:
  1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  3. 它的 左、右子树又分别为二叉排序树 。

二叉排序树插入操作:如果要插入的值比当前结点的值大,则看右孩子。若要查找的值比当前结点的值小,则看左孩子。如果要查找的值与当前结点的值相等,则该数值出现的次数加1。如果找到空结点,那么就将其插入到这个位置。
每次插入操作复杂度:O(logn)O(logn)O(logn)

输入n个数字,将每个数字插入到二叉排序树中,对二叉排序树做中序遍历,即可得到有序序列。

  • 复杂度:
    时间复杂度:O(nlogn)O(nlogn)O(nlogn)
    额外空间复杂度:O(1)O(1)O(1)
  • 稳定性:稳定
  • 代码
#include <bits/stdc++.h>
using namespace std;
#define N 100005
struct Node
{int val, ct, left, right;//val:值 ct:个数
};
Node node[N];
int p, root;
void ins(int r, int val)
{if(node[r].val == val)node[r].ct++;else if(node[r].val < val){if(node[r].right == 0){node[++p].val = val;node[p].ct = 1;node[r].right = p;}elseins(node[r].right, val);}else if(node[r].val > val){if(node[r].left == 0){node[++p].val = val;node[p].ct = 1;node[r].left = p;}elseins(node[r].left, val);}
}
void midOrder(int r)//中序遍历
{if(r == 0)return;midOrder(node[r].left);for(int i = 1; i <= node[r].ct; ++i) cout << node[r].val << ' ';midOrder(node[r].right);
}
int main()
{int n, v;cin >> n;cin >> v;node[++p].val = v;node[p].ct = 1;root = p;//树根 for(int i = 2; i <= n; ++i){cin >> v;ins(root, v);}midOrder(root);return 0;
}

11. 排序总结

三、STL中的排序函数

1. sort函数

内部原理是快速排序,不稳定,时间复杂度O(nlogn)O(nlogn)O(nlogn)
sort(起始元素指针/迭代器,最后一个元素的指针/迭代器的后一个位置, 比较函数);
对数组a下标1到n进行排序
sort(a+1,a+1+n, cmp);
对STL容器a进行排序
sort(a.begin(), a.end(), cmp);
若不写比较函数cmp,默认为升序排序。如果对结构体变量进行排序,也可以通过重载小于号运算符来指定排序规则。
比较函数的写法:传入两个数组中元素类型的量,返回传入第一个参数排在前面的条件

bool cmp(int a, int b)
{return a < b;//a < b:升序 a > b:降序
}

bool cmp(const int &a, const int &b)
{return a < b;//a < b:升序 a > b:降序
}

解决:P1177 【模板】快速排序

  • 用数组保存数据
#include <bits/stdc++.h>
using namespace std;
#define N 100005
bool cmp(const int &a, const int &b)
{return a < b;
}
int main()
{int n, a[N];cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];sort(a+1, a+1+n, cmp);//或sort(a+1, a+1+n) for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0;
}
  • 用vector保存数据
#include <bits/stdc++.h>
using namespace std;
#define N 100005
bool cmp(const int &a, const int &b)
{return a < b;
}
int main()
{vector<int> vec;int n, a;cin >> n;for(int i = 1; i <= n; ++i){cin >> a;vec.push_back(a);}sort(vec.begin(), vec.end(), cmp); for(int i = 0; i < vec.size(); ++i)cout << vec[i] << ' ';return 0;
}

2. stable_sort函数

内部原理是归并排序,稳定,时间复杂度O(nlogn)O(nlogn)O(nlogn)
stable_sort(起始元素指针/迭代器,最后一个元素的指针/迭代器的后一个位置, 比较函数);
对数组a下标1到n进行排序
stable_sort(a+1,a+1+n, cmp);
对STL容器a进行排序
stable_sort(a.begin(), a.end(), cmp);
若不写比较函数cmp,默认为升序排序。
比较函数的写法:传入两个数组中元素类型的量,返回传入第一个参数排在前面的条件

bool cmp(int a, int b)
{return a < b;//a < b:升序 a > b:降序
}

bool cmp(const int &a, const int &b)
{return a < b;//a < b:升序 a > b:降序
}

解决:P1177 【模板】快速排序

  • 用数组保存数据
#include <bits/stdc++.h>
using namespace std;
#define N 100005
bool cmp(const int &a, const int &b)
{return a < b;
}
int main()
{int n, a[N];cin >> n;for(int i = 1; i <= n; ++i)cin >> a[i];stable_sort(a+1, a+1+n, cmp);//或sort(a+1, a+1+n) for(int i = 1; i <= n; ++i)cout << a[i] << ' ';return 0;
}
  • 用vector保存数据
#include <bits/stdc++.h>
using namespace std;
#define N 100005
bool cmp(const int &a, const int &b)
{return a < b;
}
int main()
{vector<int> vec;int n, a;cin >> n;for(int i = 1; i <= n; ++i){cin >> a;vec.push_back(a);}stable_sort(vec.begin(), vec.end(), cmp); for(int i = 0; i < vec.size(); ++i)cout << vec[i] << ' ';return 0;
}

【君义精讲】排序算法相关推荐

  1. 【君义精讲】高精度计算

    一.概念 1. 高精度计算 高精度计算是指参与运算的数的范围大大超出了标准数据类型能表示的范围的运算. 如100位数字和100位数字的加减乘除运算. 为处理高精度计算,我们使用数字数组来表示高精度数字 ...

  2. 【君义精讲】多种方法求斐波那契数列

    概念 斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为"兔子数列&q ...

  3. Set精讲(Java)·算法常用集合处理方法

    Set精讲(Java)·算法常用集合处理方法 Set概述 Set集合类似于一个罐子,程序可以依次把多个对象"丢进"Set集合,而Set集合通常不能记住元素的添加顺序.实际上Set就 ...

  4. Java数据结构第一讲-排序算法

    常见数据结构和算法实现(排序/查找/数组/链表/栈/队列/树/递归/海量数据处理/图/位图/Java版数据结构) 数据结构和算法作为程序员的基本功,一定得稳扎稳打的学习,我们常见的框架底层就是各类数据 ...

  5. NOI提高级:排序算法之归并排序、快速排序

    图解排序算法(四)之归并排序 图解排序算法(四)之归并排序 - dreamcatcher-cx - 博客园 小学生图解排序算法:⑥归并排序 小学生图解排序算法:⑥归并排序_纯文笔记-CSDN博客_图解 ...

  6. 八种常用排序算法参考

    写在前面: 因为网络上有很多篇讲排序算法的,所以这里就不详细讲了,只作为参考和自己查阅 当然了,虽然篇幅也会短很多,但部分重点和要点还在 八种排序算法分别是: ①选择排序: ②冒泡排序: ③插入排序: ...

  7. List精讲(Java版)·算法常用集合处理方法

    List精讲(Java版)·算法常用集合处理方法 请仔细查阅每一个注释: import java.util.ArrayList; import java.util.Collections; impor ...

  8. Java入门算法(动态规划篇2:01背包精讲)

    本专栏已参加蓄力计划,感谢读者支持❤ 往期文章 一. Java入门算法(贪心篇)丨蓄力计划 二. Java入门算法(暴力篇)丨蓄力计划 三. Java入门算法(排序篇)丨蓄力计划 四. Java入门算 ...

  9. 数学建模-MATLAB算法精讲系列文章目录介绍(持续补充ing)

    前言 结合实际案例,从算法背景开始一步步到最终代码实现,本系列文章主要以matlab代码为主,为照顾学习其他编程语言的小伙伴,大部分算法会附带python.Java.C++.R语言等市面上主流代码,满 ...

最新文章

  1. translucent可以设置中文吗_物联卡可以作无线网卡使用吗?物联卡笔记本上网设置流程...
  2. amp 符号 php,php中引用符号(amp;)的使用详解_PHP教程
  3. 硬核|定时任务的10种实现方案,满足你的不同需求!
  4. linux 系统盘无法ls,系统故障排除
  5. 网站访客系统php,PHP实现网站访客来访显示访客IP浏览器操作系统
  6. 高擎信息安全大旗,打造“互联网+”新服务模式
  7. 【Kafka】Failed to send data to Kafka: Failed to update metadata after 60000 ms
  8. 漫画:996 的本质是什么?
  9. python实现客户端之间的通信_基于Python的服务端多线程与多客户端的通信过程
  10. n阶方阵的蛇形排列java_「P·R·N·D」的排列顺序为何成为行业标准,能不能改变呢?...
  11. 新建文件夹的快捷键大全
  12. swiper / 移动端触摸滑动插件 / 手机轮播插件
  13. Echart中series自定义formatter文字样式
  14. 在新加坡工作会是一个什么体验
  15. [Python] 网络设备巡检脚本
  16. numpy部份函数或命令用法(不定时更新)
  17. 小米,山寨杀手,国货的希望
  18. 【Unity3D 常用插件】Haste插件
  19. Codeforces Round #703 (Div. 2)C1C2 Guessing the Greatest(二分)
  20. 人工智能专业就业方向盘点

热门文章

  1. 如何下载Lucene.net源码
  2. sqlserve 热备用状态更新_燃气地暖一个月费用多少钱?看完收藏备用
  3. 终极解密输入网址按回车到底发生了什么
  4. 设计抗100亿请求的春晚红包系统
  5. 阿里P9专家右军:大话软件质量稳定性
  6. Element-UI Form表单 resetFields() 重置表单无效问题
  7. 轻量级J2EE持久层解决方案,MiniDao-PE版 1.5.4【版本发布】
  8. MySQL基础篇(03):系统和自定义函数总结,触发器使用详解
  9. USACO翻译:USACO 2012 FEB Silver三题
  10. 第41章 实施数据库审计