排序算法
本博客排序顺序默认从小到大,非降序,数组如无特殊说明则从下标1开始到下标n
插入排序
直接插入排序
插入排序顾名思义,插入,排序,
先将前i-1个序列作为一个顺序序列,然后把第i个元素插入这个序列中,得到一个长度为i的序列
同时这种做法,因为是从后往前插入,故得到的是稳定排序
故整体时间复杂度为O(n^2),空间复杂度为O(1),
最好的情况为O(n),即元素基本有序,不需要插入太多,即for循环里面的for没怎么用过
具体代码实现如下:
void Direct_insert_sort(ElemType a[], int n){
for(int i = 2; i <= n; i++){
int pos = 1, tmp = a[i]; // pos为插入的位置,默认为1,tmp表示要插入的数
for(int j = i-1; j >= 1; j--){ // 从后往前查找第一个小于tmp的数以保证稳定性
if(tmp>a[j]){
pos = j+1;break; // 找到之后,tmp的实际位置为j+1,跳出循环
}
}
for(int j = i; j > pos; j--){ // 从后往前移动位置
a[j] = a[j-1];
}
a[pos] = tmp; //更新要插入的位置
/*for(int j = 1; j <= i; j++) printf("%d ", a[j]);
printf("\n");*/
}
}
折半插入排序
和插入排序的区别是查找第i个元素在i-1个序列中的位置的方法是折半查找,这一个循环的复杂度降了,为O(logn)
但之后移动元素的复杂度依然是O(n)的复杂度
故整体时间复杂度为O(n^2),空间复杂度为O(1)
最好时间复杂度和直接插入同理,也是O(n)
但折半插入排序感觉没有什么优化,因为平均复杂度还是O(n^2)
具体实现代码如下:
void Binary_insert_sort(ElemType a[], int n){
for(int i = 2; i <= n; i++){
int l = 1, r = i-1, tmp = a[i]; // 在1~i-1中查找tmp的位置,l为其最终位置
while(l<=r){
int mid = (l+r)>>1;
if(a[mid]>tmp) r = mid-1;
else l = mid+1;
}
for(int j = i; j > l; j--){
a[j] = a[j-1];
}
a[l] = tmp;
/*for(int j = 1; j <= i; j++) printf("%d ", a[j]);
printf("\n");*/
}
}
希尔排序
个人认为乱排,勿喷
时间复杂度未知
方法为取增量d(或者说为步长也行),d可变,对序列a的子序列a[i,i+d,i+2d,…,i+kd],进行插入排序,
当d为1时,即为直接插入排序,得到不下降的序列,
或者当d为较小时,进行直接插入排序,因为此时的序列已经是基本有序序列,复杂度可降低到O(n)
时间复杂度未知,空间复杂度O(1),非稳定排序
具体实现代码如下:
void Sheel_sort(ElemType a[], int n){
for(int d = n/2; d >= 1; d/=2){ // 增量的取值可以任意,这里取为n/2,即logn的复杂度
for(int i = d+1; i <= n; i++){ // 对其中各个子序列进行排序
if(a[i]<a[i-d]){
int tmp = a[i], pos = 0;
for(int j = i-d; j >= 1 && a[j]>tmp; j -= d){ // 进行交换位置
a[j+d] = a[j];
pos = j;
}
a[pos] = tmp; // 进行插入
}
}
/*for(int j = 1; j <= n; j++) printf("%d ", a[j]);
printf("\n");*/
}
}
交换排序
冒泡排序
很经典的排序方法了,就是一个一个往上走
时间复杂度为O(n^2),空间复杂度为O(1)
具体实现代码如下:
void Bubble_sort(ElemType a[], int n){
for(int i = 1; i <= n; i++){
bool flag = 1;
for(int j = n; j > i; j--){ // 从后往前走,把当前子序列中最小的数推到第i个位置上,以此类推
if(a[j]<a[j-1]){
int tmp = a[j];
a[j] = a[j-1];
a[j-1] = tmp;
flag = 0;
}
}
if(flag) break; // 减少时间复杂度,避免不必要的比较发生
}
}
快速排序
效率最高的排序方法,分治思想
方法为以一个元素为中心把序列划分为两部分,大于这个元素和小于这个元素,
然后再对两边分别划分,以此类推,也可说是有递归的思想
时间复杂度为O(nlogn),空间复杂度为O(n)
最坏情况为O(n^2)
具体实现代码如下:
int Partition(ElemType a[], int l, int r){ // 对a进行划分
ElemType pivot = a[l]; // 默认选择的元素为第一个,即a[l],即中心
while(l<r){ // 跳出条件
while(l<r && a[r]>=pivot) --r; // 如果a[r]比中心大,则不动,同时r--,之后出现比中心小的再进行交换
a[l] = a[r]; // 比中心小的移动到左端,
// 注意这里的赋值是直接的,因为a[l]当前的值存储在pivote中
while(l<r && a[l]<=pivot) ++l; // 如果a[l]比中心小,则不动,同时l++,之后出现比中心大的再进行交换
a[r] = a[l]; // 比中心大的移动到右端
// 与上面同理
}
a[l] = pivot; // 中心最终存放的位置l
return l;
}
void Quick_sort(ElemType a[], int l, int r){
if(l<r){
int pos = Partition(a, l, r); // 将序列a分为两个部分,确定一个元素的位置
Quick_sort(a, l, pos-1); // 对前半部分进行排序
// 注意这里是pos+1不是pos,因为pos的位置已经确定了,再进行一次划分会导致死循环
Quick_sort(a, pos+1, r); // 对后半部分进行排序
}
}
选择排序
简单选择排序
没什么好说的,和冒泡排序的差别就是,冒泡是一趟交换多次交换,选择是一趟只交换一次
写冒泡排序的时候就差不多可以想到这种方法,因为冒泡也是在子序列中找一个最小的放到前面,只不过在寻找的过程中会进行元素的交换,
而简单选择排序是在子序列中直接找到一个最小的元素,然后进行交换
具体代码如下:
void Simple_select_sort(ElemType a[], int n){
for(int i = 1; i <= n; i++){
int min = i;
for(int j = i+1; j <= n; j++){
if(a[j]<a[min]) min = j;
}
if(min!=i){
swap(a[i], a[min]);
}
}
}
堆排序 待补
归并排序 待补
基数排序 待补
堆排序,归并排序,基数排序这三种排序相对来说都是有点复杂的,就分别开一个章写把,之后会补齐链接
全部代码如下
#include <bits/stdc++.h>
using namespace std;
#define ElemType int
/*
排序顺序默认从小到大,非降序,数组如无特殊说明则从下标1开始到下标n
*/
/*
插入排序:
直接插入排序
折半插入排序
希尔排序
*/
/*
直接插入
插入排序顾名思义,插入,排序,
先将前i-1个序列作为一个顺序序列,然后把第i个元素插入这个序列中,得到一个长度为i的序列
同时这种做法,因为是从后往前插入,故得到的是稳定排序
故整体时间复杂度为O(n^2),空间复杂度为O(1)
*/
void Direct_insert_sort(ElemType a[], int n){
for(int i = 2; i <= n; i++){
int pos = 1, tmp = a[i]; // pos为插入的位置,默认为1,tmp表示要插入的数
for(int j = i-1; j >= 1; j--){ // 从后往前查找第一个小于tmp的数以保证稳定性
if(tmp>a[j]){
pos = j+1;break; // 找到之后,tmp的实际位置为j+1,跳出循环
}
}
for(int j = i; j > pos; j--){ // 从后往前移动位置
a[j] = a[j-1];
}
a[pos] = tmp; //更新要插入的位置
/*for(int j = 1; j <= i; j++) printf("%d ", a[j]);
printf("\n");*/
}
}
/*
折半插入排序
和插入排序的区别是查找第i个元素在i-1个序列中的位置的方法是折半查找,这一个循环的复杂度降了,为O(logn)
但之后移动元素的复杂度依然是O(n)的复杂度
故整体时间复杂度为O(n^2),空间复杂度为O(1)
*/
void Binary_insert_sort(ElemType a[], int n){
for(int i = 2; i <= n; i++){
int l = 1, r = i-1, tmp = a[i]; // 在1~i-1中查找tmp的位置,l为其最终位置
while(l<=r){
int mid = (l+r)>>1;
if(a[mid]>tmp) r = mid-1;
else l = mid+1;
}
for(int j = i; j > l; j--){
a[j] = a[j-1];
}
a[l] = tmp;
/*for(int j = 1; j <= i; j++) printf("%d ", a[j]);
printf("\n");*/
}
}
/*
希尔排序
乱排,时间复杂度未知
方法为取增量d(或者说为步长也行),d可变,对序列a的子序列a[i,i+d,i+2d,...,i+kd],进行插入排序,
当d为1时,即为直接插入排序,得到不下降的序列,
或者当d为较小时,进行直接插入排序,因为此时的序列已经是基本有序序列,复杂度可降低到O(n)
时间复杂度未知,空间复杂度O(1),非稳定排序
*/
void Sheel_sort(ElemType a[], int n){
for(int d = n/2; d >= 1; d/=2){ // 增量的取值可以任意,这里取为n/2,即logn的复杂度
for(int i = d+1; i <= n; i++){ // 对其中各个子序列进行排序
if(a[i]<a[i-d]){
int tmp = a[i], pos = 0;
for(int j = i-d; j >= 1 && a[j]>tmp; j -= d){ // 进行交换位置
a[j+d] = a[j];
pos = j;
}
a[pos] = tmp; // 进行插入
}
}
/*for(int j = 1; j <= n; j++) printf("%d ", a[j]);
printf("\n");*/
}
}
/*
交换排序:
冒泡排序
快速排序
*/
/*
冒泡排序
很经典的排序方法了,就是一个一个往上走
时间复杂度为O(n^2),空间复杂度为O(1)
*/
void Bubble_sort(ElemType a[], int n){
for(int i = 1; i <= n; i++){
bool flag = 1;
for(int j = n; j > i; j--){
if(a[j]>a[j-1]){
int tmp = a[j];
a[j] = a[j-1];
a[j-1] = tmp;
flag = 0;
}
}
if(flag) break;
}
}
/*
快速排序
效率最高的排序方法,分治思想
方法为以一个元素为中心把序列划分为两部分,大于这个元素和小于这个元素,
然后再对两边分别划分,以此类推,也可说是有递归的思想
*/
int Partition(ElemType a[], int l, int r){
ElemType pivot = a[l];
while(l<r){
while(l<r&&a[r]>=pivot) --r;
a[l] = a[r];
while(l<r&&a[l]<=pivot) ++l;
a[r] = a[l];
}
a[l] = pivot;
return l;
}
void Quick_sort(ElemType a[], int l, int r){
if(l<r){
int pos = Partition(a, l, r);
//Quick_sort(a, l, pos-1);
Quick_sort(a, pos, r);
}
/*
选择排序:
简单选择排序
堆排序
*/
/*
归并排序
*/
/*
基数排序
*/
/*
打印数组
*/
void Print(ElemType a[], int n){
for(int i = 1; i <= n; i++){
printf("%d ", a[i]);
}
}
int main(){
int a[] = {0,4,2,1,5,6,3,7,9,8,0}; // 第一个元素无效
int n = 10;
// Direct_insert_sort(a,n);
// Binary_insert_sort(a,n);
// Sheel_sort(a,n);
Quick_sort(a,1,n);
Print(a,n);
return 0;
}