这篇东西其实是当时为了找实习而复习排序弄的,面试官无聊就喜欢问你个排序,如果你连插入排序跟选择排序都分不清楚的话还是别去找虐了。

几种排序

大致按算法难度、类型从上到下排。

算法描述都按升序排序,复杂度都指平均复杂度。

  • 冒泡排序

    模拟气泡浮上来的过程,n-1趟float,时间复杂度O( n2 )

  • 选择排序,一般指简单选择排序

    每次在无序区中选择出最大的元素,然后放到有序区跟无序区间,n-1趟,时间复杂度O( n2 )

  • 插入排序,一般指直接插入排序,还有折半插入排序、2-路插入排序、表插入排序等

    本质:元素插入到有序列。

    左边有序区,右边无序区(待排),每次将无序区最左边的元素插入到有序区中合适的位置(故涉及到元素的右移),n-1次,时间复杂度O( n2 )

  • 希尔排序,对直接插入排序的改进

    简单来说,就是取不同步长,进行多次插入排序,最后步长为1,就是以此直接插入排序。重点在于步长的选择,原始版本步长为n/( 2i ),最坏复杂度O( n2 )。现在最好的步长可以达到O( n(logn)2 ),仅次于O(nlogn)的排序。

    可以想象成取不同的行宽,按列进行插入排序。

    当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。

  • 基数排序

    这篇文章的唯一一个非比较型排序算法。基数(Radix),即数的进制。之所以能够不进行比较,是因为它按数位将数分配到Radix个桶中,再顺序进行收集。

    有LSD(Least significant digital)、MSD(Most significant digital)两种方式。需要进行k(数据中最大位数)趟,每趟分配要O(n),收集要O(radix),所以总复杂度O(k(n+radix))。

  • 归并排序,一般指2-路归并排序,还有非递归归并排序、自然归并排序等。

    本质:分治,有序列合并。

    二分需要O(logn),合并需要O(n),总时间复杂度O(nlogn)。需要O(n)的额外空间,用于存放合并有序列的临时结果。

  • 快速排序

    本质:分治,Patition。

    难点在于Patition,要做到O(n)的时间复杂度。简单来说,就是选最左边的作为pivot,并维护值,然后两个指针,从右往左扫,从左往右扫,遇到不合适的,则换到另一边。直到两个指针相遇。

    总平均时间复杂度O(nlogn)。

  • 堆排序

    本质:堆(分治)

    升序排列的话需要借助大顶堆,涉及max_heapify、sift_down两个子操作。其中sift_down类似直接插入排序中的数组右移。

    其实说白了就是二叉堆上的插入排序。

归类

冒泡、选择、插入,都可以认为是将元素划分为有序区、无序区,都要n-1趟处理(无序区拓展到有序区),时间复杂度都是O( n2 )。

希尔,实现起来很简单,采用最佳步长的话,复杂度可以达到O( n(logn)2 ),仅次于O(nlogn)的排序。而且插入排序在元素基本有序的情况下时间复杂度接近O(n)。在一些情况下还是很有优势的。

基数,非比较型排序算法,类似的还有桶排序等。在数据有一定限制(比如都在0~1000间),数据量很大(比较型算法最快也要O(nlogn))的情况下,可以考虑非比较型算法,可以O(n)完成。

归并、快速、堆排,都应用了分治思想,平均复杂度都是O(nlogn)。这也是基于比较的排序算法的极限了。

具体实现

我希望用最简洁的代码实现算法。

因为面试一般不让写伪代码,而且是在纸上写,所以我就不用宏定义写得标准一点了。

Ps. 我原来写swap是这样写,inline int swap(int &x, int &y) { x^=y; y^=x; x^=y; },结果被坑了,why?试想x、y地址一样的话…


#include <cstdio>
#include <cstring>
#include <iostream>
#include <list>
using namespace std;
inline int swap(int &x, int &y) { int t = x; x = y; y = t; }
inline int max(int x, int y) { return (x>y)? x: y; }
inline int min(int x, int y) { return (x<y)? x: y; }
#define bug(s) cout<<#s<<"="<<s<<" "

// 冒泡,O(n^2)
void bubble_sort(int *a, int n) {
	for(int k=0; k<n-1; k++) {
		for(int i = 0; i<n-1-k; i++) {
			if(a[i]>a[i+1])
				swap(a[i], a[i+1]);
		}
	}
}

// 简单选择,O(n^2)
void selection_sort(int *a, int n) {
	for(int k=0; k<n-1; k++) {
		int maxi = 0;
		for(int i=1; i<n-k; i++) {
			if(a[maxi] < a[i])
				maxi = i;
		}
		swap(a[maxi], a[n-1-k]);
	}
}

// 直接插入,O(n^2)
void insert_sort(int *a, int n) {
	for(int i=1, j; i<n; i++) {
		int t = a[i];
		for(j=i-1; j>=0 && a[j]>t; j--) a[j+1] = a[j];
		a[j+1] = t;
	}
}

// 希尔,O(n^2)
void shell_sort(int *a, int n) {
	for(int gap = n/2; gap>0; gap/=2) {
		for(int i=gap, j; i<n; i++) {
			int t = a[i];
			for(j=i-gap; j>=0 && a[j]>t; j-=gap) a[j+gap] = a[j];
			a[j+gap] = t;
		}
	}
}

// 基数,O(k*(n+r)),假设radix=10, k=5
void radix_sort(int *a, int n) {
	list<int> l[10];
	int r = 10, k = 5;
	for(int i=0, e=1; i<k; i++, e*=10) {
		for(int j=0; j<n; j++) {
			l[a[j]/e%10].push_back(a[j]);
		}
		for(int j=0, m=0; j<r; j++) {
			while(!l[j].empty()) {
				a[m++] = l[j].front();
				l[j].pop_front();
			}
		}
	}
}

// 归并,O(nlog(n))
void merge(int *a, int l, int mid, int r, int *t) {
	int i, j, c = 0;
	for(i=l, j=mid+1; i<=mid && j<=r;) {
		if(a[i]<a[j]) t[c++] = a[i++];
		else t[c++] = a[j++];
	}
	while(i<=mid) t[c++] = a[i++];
	while(j<=r) t[c++] = a[j++];
	for(c=0; c<r-l+1; c++) a[l+c] = t[c];
}
void m_sort(int *a, int l, int r, int *t) {
	if(l<r) {
		int mid = (l+r)>>1;
		m_sort(a, l, mid, t);
		m_sort(a, mid+1, r, t);
		merge(a, l, mid, r, t);
	}
}
void merge_sort(int *a, int n) {
	int *t = (int*)malloc(sizeof(int)*n);	// 统一用一个临时空间
	m_sort(a, 0, n-1, t);
	free(t);
}

// 快速排序,O(nlogn)
int patition(int *a, int l, int r) {
	int t = a[l];	//pivot
	while(l<r) {
		while(l<r && a[r]>=t) r--;
		a[l] = a[r];
		while(l<r && a[l]<=t) l++;
		a[r] = a[l];
	}
	a[l] = t;
	return l;
}
void q_sort(int *a, int l, int r) {
	if(l<r) {
		int q = patition(a, l, r);
		q_sort(a, l, q-1);
		q_sort(a, q+1, r);
	}
}
void quick_sort(int *a, int n) {
	q_sort(a, 0, n-1);
}

// 堆排序,O(nlogn)
#define lson(e) (e)<<1|1
void sift_down(int *a, int n, int i) {	// 类似直接插入排序的sift
	int t = a[i];
	for(int j=lson(i); j<n; i=j, j=lson(i)) {
		if(j+1<n && a[j]<a[j+1]) j++;	// 取较大的节点
		if(t>a[j]) break;
		a[i] = a[j];
	}
	a[i] = t;
}
void max_heapify(int *a, int n) {
	for(int i=n/2-1; i>=0; i--) {
		sift_down(a, n, i);
	}
}
void heap_sort(int *a, int n) {
	max_heapify(a, n);
	for(int i=n-1; i>=1; i--) {
		swap(a[0], a[i]);
		sift_down(a, i, 0);
	}
}

int main() {
	int a[] = {5,4,3,1,6,8,9,16,15};
	int n = sizeof(a)/sizeof(a[0]);
	// bubble_sort(a, n);
	// selection_sort(a, n);
	// insert_sort(a, n);
	// shell_sort(a, n);
	// radix_sort(a, n);
	// merge_sort(a, n);
	// quick_sort(a, n);
	heap_sort(a, n);
	for(int i=0; i<n; i++) {
		printf("%d ", a[i]);
	}
	// 1 3 4 5 6 8 9 15 16
}