排序算法总结

sort

绝大多数时候,我们直接使用头文件 中的 sort 函数对数组进行排序。

#include 
#include 
const int N = 1007;
int a[N];
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);
    //默认从小到大排序,第一个参数是待排序中第一个元素的地址,第二个参数是最后一个元素的地址的下一个位置
    std::sort(a+1, a+n+1);
    for (int i = 1; i <= n; ++i)
        printf("%d ", a[i]);
    return 0;
}
5
3 7 1 0 4
0 1 3 4 7

要改成从大到小排序,需要加一个头文件和一个参数:

#include 
#include 
#include 
const int N = 1007;
int a[N];
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);
    //默认从小到大排序,第一个参数是待排序中第一个元素的地址,第二个参数是最后一个元素的地址的下一个位置
    std::sort(a+1, a+n+1, std::greater<int>());
    for (int i = 1; i <= n; ++i)
        printf("%d ", a[i]);
    return 0;
}
5
3 7 1 0 4
7 4 3 1 0

我们也可以单独使用一个比较函数 cmp 来进行排序,例如,我们要对 n 个数进行如下规则的排序:

  • 所有的奇数排在前面,所有的偶数排在后面;

  • 奇数按从小到大排序,偶数按从大到小排序。

#include 
#include 
const int N = 1007;
int a[N];
bool cmp(const int &a, const int &b) { 
    if (a%2==1 && b%2==0) return true;
    if (a%2==1 && b%2==1 && a<b) return true;
    if (a%2==0 && b%2==0 && a>b) return true;
    return false;
}

int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);
    //第三个参数为比较函数名
    std::sort(a+1, a+n+1, cmp);
    for (int i = 1; i <= n; ++i)
        printf("%d ", a[i]);
    return 0;
}
10
1 2 3 4 5 6 7 8 9 10
1 3 5 7 9 10 8 6 4 2

注意:sort 采用快速排序算法,不是稳定的排序算法

排序的稳定性

如果数组中有 2 个元素的数值相同,排序后这 2 个元素的前后顺序保持不变,则我们称这样的排序是稳定的。

选择排序(不稳定)

时间复杂度:

最好: O ( n 2 ) O(n^2) O(n2),最坏: O ( n 2 ) O(n^2) O(n2)

//C语言版
#include 
int a[607];
int main(){
	int n, i, id, j, t;
	scanf("%d", &n);
	for (i = 1; i <= n; ++i) scanf("%d", &a[i]);
	//选择排序
	for (i = 1; i < n; ++i) {
		id = i;
		for (j = i + 1; j <= n; ++j)
			if (a[j] < a[id]) id = j;
		//交换a[id] 和 a[i] 
		if (id != i) 
			t = a[id], a[id] = a[i], a[i] = t;
	}
	printf("%d %d %d", a[1], a[2], a[3]);
	return 0;
}
#include 
#include 
int a[107];
void select_sort(int a[], int n) {
    //a并非数组,而是一个指针,指向数组首地址
    //该函数内的a与全局变量a数组是两个不同的变量
	for (int i = 1; i < n; ++i) {
		int id = i;
		for (int j = i + 1; j <= n; ++j)
			if (a[j] < a[id]) id = j;
		if (id != i) std::swap(a[i], a[id]);
	}
}
int main() {
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", a + i);
	select_sort(a, n);
	for (int i = 1; i <= n; ++i) printf("%d ", a[i]);
	return 0;
}
10
8 2 5 9 7 1 3 4 6 0
0 1 2 3 4 5 6 7 8 9

选择排序不稳定的原因:

看如下的一个例子:

(Jason, 287), (Ada, 200), (Mike, 287), (Cindy, 100)

第一步选出最小值 (Cindy, 100),数组变为 (Cindy, 100), (Ada, 200), (Mike, 287), (Jason, 287)

排序完成,两个287分的同学,相对位置发生了变化。

由于在排序中,无法保证相等的两个元素相对位置不变,我们称选择排序为不稳定的排序。

插入排序(稳定)

插入排序的算法思想源于扑克牌游戏。

排序算法总结_第1张图片
排序算法总结_第2张图片
排序算法总结_第3张图片

时间复杂度:

最好: O ( n ) O(n) O(n),最坏: O ( n 2 ) O(n^2) O(n2)

#include 
int a[607];
int main(){
	int n, i, id, j, k;
	scanf("%d", &n);
	for (i = 1; i <= n; ++i) scanf("%d", &a[i]);
	//插入排序
	for (i = 2; i <= n; ++i) {
        //把大于a[i]的数都往后挪
		for (j=i-1, k=a[i]; j && a[j]>k; --j)
            a[j+1] = a[j];
        a[j+1] = k;
	}
	printf("%d %d %d", a[1], a[2], a[3]);
	return 0;
}
#include 
int a[107];
void insert_sort(int a[], int n) {
	for (int i = 2, j, t; i <= n; ++i) {
		for (j = i-1, t = a[i]; j && t<a[j]; --j)
			a[j+1] = a[j];
		a[j+1] = t;
	}
}
int main() {
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", a + i);
	insert_sort(a, n);
	for (int i = 1; i <= n; ++i) printf("%d ", a[i]);
	return 0;
}

冒泡排序(稳定)

时间复杂度:

最好: O ( n 2 ) O(n^2) O(n2),最坏: O ( n 2 ) O(n^2) O(n2)

#include 
int a[607];
int main(){
	int n, i, j, t;
	scanf("%d", &n);
	for (i = 1; i <= n; ++i) scanf("%d", &a[i]);
	//冒泡排序
	for (i = n-1; i; --i) {
        for (j = 1; j <= i; ++j)
            if (a[j] > a[j+1])
                t = a[j], a[j] = a[j+1], a[j+1] = t;
	}
	printf("%d %d %d", a[1], a[2], a[3]);
	return 0;
}

注意:若某一轮未发生交换,则说明已完成排序

时间复杂度:

最好: O ( n ) O(n) O(n),最坏: O ( n 2 ) O(n^2) O(n2)

#include 
#define swap(a,b,t) {t=a,a=b,b=t;}
int a[607];
int main(){
	int n, i, j, t, flag;
	scanf("%d", &n);
	for (i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (i = n-1; i; --i) {
        flag = 1;
        for (j = 1; j <= i; ++j)
            if (a[j] > a[j+1]) {
                swap(a[j], a[j+1], t)
                flag = 0;
            }
        if (flag) break;
	}
	printf("%d %d %d", a[1], a[2], a[3]);
	return 0;
}
#include 
inline void swap(int &a, int &b) { int t = a; a = b; b = t; }
int a[107];
void bubble_sort(int a[], int n) {
	for (int i = n-1; i; --i) {
		int cnt = 0;//cnt为本轮逆序对数
		for (int j = 1; j <= i; ++j)
			if (a[j] > a[j+1]) 
				swap(a[j], a[j+1]), ++cnt;
		if (!cnt) break;
	}
}
int main() {
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", a + i);
	bubble_sort(a, n);
	for (int i = 1; i <= n; ++i) printf("%d ", a[i]);
	return 0;
}

计数排序(稳定)

#include 
//N 为个数,M 为取值范围[0, M]
//M<=kN,即 N 的整数倍,速度最快,最好是 N 远大于 M;
const int N = 107, M = 1e3 + 7;
int a[N], c[M], ans[N];
void count_sort(int a[], int n) {
    for (int i = 1; i <= n; ++i) ++c[a[i]];
    for (int i = 1; i < M; ++i)
        c[i] += c[i-1];
    for (int i = n; i; --i)
        ans[c[a[i]]--] = a[i];
}
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);
    count_sort(a, n);
    for (int i = 1; i <= n; ++i)
        printf("%d ", ans[i]);
    return 0;
}

基数排序(稳定)

// int 范围内的非负整数排序
#include 
#include 
const int N = 1e5 + 7;
int a[N], c[260], n, b[N];
void rsort() {
    for (int k = 0; k < 32; k += 8) {
        memset(c, 0, sizeof c);
        for (int i = 1; i <= n; ++i) ++c[a[i]>>k & 0xFF];
        for (int i = 1; i <= 0xFF; ++i) c[i] += c[i-1];
        for (int i = n; i; --i) {
            int t = a[i]>>k & 0xFF;
            b[c[t]--] = a[i];
        }
        for (int i = 1; i <= n; ++i) a[i] = b[i];
    }
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", a + i);
    rsort();
    for (int i = 1; i <= n; ++i) printf("%d ", a[i]);
    puts("");
    return 0;
}

桶排序

普通情况的单值排序,只要取值范围不大于元素个数,就可以使用桶排序。每个元素的值对应一个桶,桶里存放一个数:

  • true(对应这个值在数组中存在),false(对应这个值不在数组中)
  • 一个整数,表示这个数在数组中出现的次数。

例1:给 7 1 9 2 3 排序。

i 1 2 3 4 5
a[i] 7 1 9 2 3

bucket 的首字母 b 作为桶数组的名称。

i 0 1 2 3 4 5 6 7 8 9
b[i] 0 1 1 1 0 0 0 1 0 1

上面如同 10 10 10 个桶,编号分别为 0 ∼ 9 0\sim 9 09,如果我们只记录每个数是否出现,则 b b b 数组可使用 bool 类型;如果要记录每个数出现的次数, b b b 数组就需要用 int 类型。
要输出排序结果,只需要从 0 ∼ 9 0\sim 9 09 依次检查每个桶是否为 1 1 1,如果为 1 1 1 就输出对应的桶的编号 i i i

例2:输出 1 2 1 2 1 排序后的结果,重复数字不需要输出
方法同上一个例子。

#include 
using namespace std;
const int N = 107;
int a[N];
bool b[N];
int main() {
	int n;
	scanf("%d", &n);
	for (int i = 1, x; i <= n; ++i) {
		scanf("%d", &x);
		b[x] = true;
	}
	// 注意对桶的循环范围
	for (int i = 0; i < 10; ++i)
		if (b[i]) printf("%d ", i);
}

例3:输出 1 2 1 2 1 排序后的结果。
如果排序后多数字需要多次输出,则我们必须在桶里给每个数计数。

#include 
using namespace std;
const int N = 107;
int a[N], b[N];
int main() {
	int n;
	scanf("%d", &n);
	for (int i = 1, x; i <= n; ++i) {
		scanf("%d", &x);
		++b[x];
	}
	// 注意对桶的循环范围
	for (int i = 0; i < 10; ++i)
		for (int j = 1; j <= b[i]; ++j) 
			printf("%d ", i);
}

那么,当排序的数字有小数怎么办呢?这个时候就需要二维数组来存放了。
排序算法总结_第4张图片

#include 
#include 
using namespace std;
const int N = 107;
double a[N], f[N][N];
void buck_sort(double a[], int n) {
    for (int i = 1, j; i <= n; ++i) {
		int u = int(a[i] * 10) % 10;
		for (j = f[u][0]++; j && f[u][j]>a[i]; --j)
			f[u][j+1] = f[u][j];
		f[u][j+1] = a[i];
	}
}
int main() {
	freopen("1.in", "r", stdin);
	int n;
	scanf("%d", &n);
	for (int i = 1, j; i <= n; ++i)
		scanf("%lf", a + i);
	buck_sort(a, n);
	for (int i = 0; i <= 9; ++i)
		for (int j = 1; j <= f[i][0]; ++j)
			printf("%.2f ", f[i][j]);
	return 0;
}

归并排序(稳定)

#include 
typedef long long LL;
const int N = 1e5 + 7;
int a[N], b[N];
LL msort(int lo, int hi) {
    if (lo == hi) return 0;
    int mid = (lo + hi) >> 1;
    //ans记录逆序对的个数
    LL ans = msort(lo, mid) + msort(mid+1, hi);
    //i合并后的新编号,j左半边的编号,k右半边的编号
    for (int i=lo, j=lo, k=mid+1; i <= hi; ++i) {
        if (j > mid) { b[i] = a[k++]; continue; }
        if (k > hi) { b[i] = a[j++]; continue; }
        if (a[j] <= a[k]) { b[i] = a[j++]; continue; }
        ans += mid - j + 1, b[i] = a[k++];
    }
    for (int i = lo; i <= hi; ++i) a[i] = b[i];
    return ans;
}
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", a + i);
    LL ans = msort(1, n);
    printf("%lld", ans);
    return 0;
}

堆排序(heap_sort(int c[], int n))

#include 
#include 
const int N = 107;
int c[N], n;
void adjust(int cur, int n) {
    int x = c[cur], chd = cur << 1;
    while (chd <= n) {
        if (chd+1<=n && c[chd]<c[chd+1]) ++chd;
        if (c[chd] > x) c[cur] = c[chd];
        else break;
        cur = chd, chd <<= 1;
    }
    c[cur] = x;
}
void heap_sort(int c[], int n) {
    for (int i = n>>1; i; --i) adjust(i, n);
    for (int i = n, j; i > 1;) {
        std::swap(c[i--], c[1]);
        adjust(1, i);
    }
}
int main() {
    freopen("1.in", "r", stdin);
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", c + i);
    heap_sort(c, n);
    for (int i = 1; i <= n; ++i)
        printf("%d ", c[i]);
    return 0;
}

快速排序(不稳定)

#include 
#include 
const int N = 1e5 + 7;
int a[N];
void qsort(int lo, int hi) {
    int i = lo, j = hi, mid = a[lo+hi>>1];
    //分割操作,lo~j均不大于mid,i~hi均不小于mid
    while (i <= j) {
        while (a[i] < mid) ++i;
        while (a[j] > mid) --j;
        if (i <= j) std::swap(a[i++], a[j--]);
    }
    if (lo < j) qsort(lo, j);
    if (i < hi) qsort(i, hi);
}
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", a + i);
    qsort(1, n);
    for (int i = 1; i <= n; ++i) printf("%d ", a[i]);
    return 0;
}

希尔排序(Shell_sort(int a[], int n))

  1. Hibbard 增量序列, D k = 2 k − 1 D_k=2^k-1 Dk=2k1,相邻元素互质
    • 最坏情况: T = θ ( N 3 / 2 ) T=\theta(N^{3/2}) T=θ(N3/2)
    • 猜想: T a v g = O ( N 5 / 4 ) T_{avg}=O(N^{5/4}) Tavg=O(N5/4)
  2. Sedgewick 增量序列
    • { 1 , 5 , 19 , 41 , 109 , …   } \{1,5,19,41,109,\dots \} {1,5,19,41,109,},由 9 × 4 i − 9 × 2 i + 1 9\times 4^i-9\times 2^i+1 9×4i9×2i+1 4 i − 3 × 2 i + 1 4^i-3\times 2^i+1 4i3×2i+1 按序取出的。
    • 猜想: T a v g = O ( N 7 / 6 ) , T w o r s t = O ( N 4 / 3 ) T_{avg}=O(N^{7/6}), T_{worst}=O(N^{4/3}) Tavg=O(N7/6),Tworst=O(N4/3)
#include 
#include 
const int N = 107, INF = 2e9;
int s[30], c, a[N];
// void Shell_sort(int a[], int n) {
// 	//为保持增量元素互质,避免一竖列已排序时,小增量不起作用,白用功
// 	//采用Hibbard增量序列,Dk = 2^k - 1, O(n^(4/3))
// 	for (int i = 1<<((int)floor(log(n)/log(2)))-1; i; i = (i+1>>1)-1)
// 		for (int j = i+1, k, x; j <= n; ++j) {
// 			for (k = j-i, x = a[j]; k>0 && x
// 				a[k+i] = a[k];
// 			a[k+i] = x;
// 		}
// }
int calc() {
	//计算Sedgewick增量序列,u(i)=9*4^i-9*2^i+1和v(i)=4^i-3*2^i+1
	//恰好构成差位为2的递增序列,u(i) < v(i+2), O(n^(7/6))
	for (int i = 0; i <= 13; ++i) {
		int u = 9*(1<<(i<<1))-9*(1<<i)+1;
		int t = i + 2, v = (1<<(t<<1))-3*(1<<t)+1;
		if (u>=1 && u<=INF) s[++c] = u;
		if (v>=1 && v<=INF) s[++c] = v;
	}
}
void Shell_sort(int a[], int n) {
	//为保持增量元素互质,避免一竖列已排序时,小增量不起作用,白用功
	//采用Sedgewick增量序列
	while (s[c] >= n) --c;
	do {
		int u = s[c];
		for (int j = u+1, k, x; j <= n; ++j) {
			for (k = j-u, x = a[j]; k>0 && x<a[k]; k -= u)
				a[k+u] = a[k];
			a[k+u] = x;
		}
	} while (--c);
}
int main() {
	freopen("1.in", "r", stdin);
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", a + i);
	calc();
    Shell_sort(a, n);
	for (int i = 1; i <= n; ++i) printf("%d ", a[i]);
	return 0;
}

你可能感兴趣的:(数组,排序算法,算法,数据结构)