目录
一 插入排序 (自己内部交换 空间复杂度一般为 1)
1. 直接插入排序
2. 折半插入排序
3.希尔(shell)排序
二. 交换排序
1.冒泡排序
2 . 快速排序
三. 选择排序
1.简单(直接)选择排序
2 . 堆排序
四. 归并排序
总结:
步骤1:
将待排序的部分放入一个数组 a[i] 从1开始是一个有序的部分,后面是无序的部分(0是哨兵防止越界也可当一个缓存单元交换用)
步骤2:
循环n-1次,每次使用顺序查找法 在a数组未排好的部分寻找最小值(我们设从小到大排),然后插入到前面的部分,在这个过程中需要进行数组内元素位置的调整。
因为比较简单在这里就不写代码了
时间复杂度是 O(n^2) 空间复杂度是O(1) 没有使用额外空间
注:该算法稳定 并且适合排较有序的数据。 可用于链式结构
在上面步骤二中 寻找最小值 是算法时间复杂度较高的原因之一(移动则是另一个) 因此我们可以用二分查找(折半查找)来优化
具体代码不表
时间复杂度 还是O(n^2)但是肯定会比直接法快一些
注:不适用于链式结构 适用于无序的数据 且稳定
希尔排序实质上是分组插入的方法,将其分成几部分进行排序
最后变成基本有序 然后进行直接插入排序
步骤一:取增量a 将其分为 Sum/a 组在各组进行直接插入排序 根据排序将其填入挖出的空中
步骤二:取增量b 重复步骤1 直到增量为1
增量的取法影响其效率
hibbard增量序列 Dk=2^k-1 (取的增量互质)
时间复杂度 时间复杂度可变成O(n ^ 3/2 )
注: 不稳定 不可用于链式
适合数据大且无序
最经典的排序算法
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来
说并没有什么太大作用。
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
//基础版
void bubble_sort(int arr[], int len) {
int i, j, temp;
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1]) {
swap(a[j],a[j+1);
}
}
注:稳定,可用于链式,不适合大规模无序数据
在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
快排的主要思想是分治,本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。
步骤一:
从数列中挑出一个元素,称为 "基准"(pivot);
步骤二:
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
步骤三: 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
直到递归元素只有一个
根据教材的讲解一般是以最左方为基准元素,对high low指针进行操作
当high>=基准值
low<=基准值 时进行交换
high和low相遇时退出
如序列 49 38 65 97 76 13 27
我们以49为基准L指针 27为H指针 27 和49 交换
得到 27 38 65 97 76 13 49
然后H指针不动 low指针右移找到 65>49 停下 交换二者
得到 27 38 49 97 76 13 65
H指针左移到13 交换
得到 27 38 13 97 76 49 65
然后L指针右移 到97
得到 27 38 13 49 76 97 65
H指针与L相遇结束
第一趟就是
27 38 13 49 76 97 65
以此类推
第二趟是 以49 为分割进行分治排序
(13 27 38) 49 (65 76 97)
代码如下
//严蔚敏《数据结构》标准分割函数
int Paritition1(int A[], int low, int high) {
int pivot = A[low];
while (low < high) {
while (low < high && A[high] >= pivot) {
--high;
}
A[low] = A[high];
while (low < high && A[low] <= pivot) {
++low;
}
A[high] = A[low];
}
A[low] = pivot;
return low;
}
void QuickSort(int A[], int low, int high) //快排母函数
{
if (low < high) {
int pivot = Paritition1(A, low, high);
QuickSort(A, low, pivot - 1);
QuickSort(A, pivot + 1, high);
}
}
//算法中常用裸结构
//选中间为轴
void quick_sort(int l,int r)
{
if(l>=r)return ;
int i=l-1,j=r+1;
int mid=a[l+r>>1];
while(imid);
if(i
由于是递归 所以空间复杂度为 n
快速排序的最坏运行情况是 O(n²) 但它的平摊期望时间是 O(nlogn)
注:不适合链式,不稳定 但大部分情况快 所以适合 无序 数据量大 的情况
选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。(和冒泡差不多)
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。
// pro max 版
int a[n]//设里面装满了数据
//小到大
for(int i=0;ia[j])
swap(a[i],a[j]);
//代码中的for循环嵌套用于比较数组中相邻的元素,如果前一个元素大于后一个元素,则交换它们的位置,这样每一轮循环结束后,数组中最小的元素就会被排到最前面
由于和冒泡差不多所以
注:稳定,可用于链式,不适合大规模无序数据
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
堆排序的平均时间复杂度为 Ο(nlogn)。
创建一个堆 H[0……n-1];
把堆首(最大值)和堆尾互换;
把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
重复步骤 2,直到堆的尺寸为 1。
主要难点是建立堆和堆的维护代码如下:
//如何手写一个堆?完全二叉树 5个操作
//1. 插入一个数 heap[ ++ size] = x; up(size);
//2. 求集合中的最小值 heap[1]
//3. 删除最小值 heap[1] = heap[size]; size -- ;down(1);
//4. 删除任意一个元素 heap[k] = heap[size]; size -- ;up(k); down(k);
//5. 修改任意一个元素 heap[k] = x; up(k); down(k);
#include
#include
#include
using namespace std;
const int N = 1e5+10;
int h[N];
int siz;
void down(int u)
{
int t=u;
if(u*2<=siz&&h[u*2]>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d",&h[i]);
}
siz=n;
for(int i=n/2;i;i--)//小根堆
{
down(i);
}
while (m -- )
{
printf("%d ", h[1]);
h[1] = h[siz];
siz --;
down(1);
}
}
当然,我们也可以用stl库里面的函数简单粗暴的写
//标准堆排序
int main()
{
vectorq;
int n,m;
cin>>n>>m;
while (n -- )
{
int t;
cin>>t;
q.push_back(t);
}
make_heap(q.begin(),q.end());
sort_heap(q.begin(),q.end());
int cnt=0;
for(auto t:q)
{
if(++cnt>m)break;
cout<,greater>q;
//这里是小根堆
//大根堆是 priority_queue q;
int n,m;
cin>>n>>m;
while (n -- )
{
int t;
cin>>t;
q.push(t);
}
while (m -- )
{
cout<
当然优先队列和堆不能混为一谈 但是实现优先队列中二叉树是一种和重要的方式 因此在这里和堆的功能差不多并且时间效率较高(手写的最高)
时间复杂度nlogn
空间为1
注:不稳定,不适用链式
归并排序的思想也是分治算法,将其分为小块排序然后归并
代码如下
void merge_sort(int q[], int l, int r)
{
//递归的终止情况
if(l >= r) return;
//第一步:分成子问题
int mid = l + r >> 1;
//第二步:递归处理子问题
merge_sort(q, l, mid ), merge_sort(q, mid + 1, r);
//第三步:合并子问题
int k = 0, i = l, j = mid + 1, tmp[r - l + 1];
while(i <= mid && j <= r)
if(q[i] <= q[j]) tmp[k++] = q[i++];
else tmp[k++] = q[j++];
while(i <= mid) tmp[k++] = q[i++];
while(j <= r) tmp[k++] = q[j++];
for(k = 0, i = l; i <= r; k++, i++) q[i] = tmp[k];
}
其中第三步是主要实现部分,是将分成的两部分归并的核心
在这里贴一道题帮助理解 788. 逆序对的数量 - AcWing题库
时间复杂度 nlogn 空间复杂度 n
注:稳定 可用于链式