以下是我自己总结有关排序算法的重要知识点,有兴趣的小伙伴可以下载图片观看呀!
下载链接在此!
以下是我学习时,写的相关排序的C/C++代码(混合着用了/(ㄒoㄒ)/,只是用到了C的printf,两者基本上一样的,挺容易看懂的)
#include<stdio.h>
#include<iostream>
//#include<algorithm> //c中没有,所以自己写了swap函数
using namespace std;
void swap(int *a, int *b);
//冒泡
void pop_sort(int *a, int len);
//插入排序
void Insert_sort(int *a, int N);
void Insert_zheban(int a[], int n);
//希尔排序及优化算法
void Shell_sort(int *a, int N);
void Sedgewick_shell_sort(int *a, int N);
void Hibbard_shell_sort(int *A, int N);
//简单选择排序
void Selection_sort(int *a, int N);
int ScanForMin(int *a, int i,int N);
//堆排序
void Heap_sort(int *a, int N);
void adjust_node(int *arr, int n, int len);
//归并排序
void merge(int *a, int L, int mid, int R);
void Merge_sort(int *a, int l, int r);
//快速排序
int getpivot(int *a, int L, int R);
void Quicksort(int *a, int left, int right);
void Quick_sort(int *a, int N);
void BaseSort(int *a, int N);
int main(void) {
int a[10] = {3,5,7,6,8,9,1,4,2,0};
for (int i=0; i<10; ++i) {
printf("%d ", a[i]);
}
printf("\n");
///
//Heap_sort(a, 10);
Merge_sort(a, 0, 9);
///
for (int i=0; i<10; ++i) {
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
void swap(int *a, int *b) {
int t;
t = *a;
*a = *b;
*b = t;
}
//冒泡排序,加入flag标志,优化效率
void pop_sort(int *a, int len) {//*(a+i)等价于 a[i]
int i,j;
for (i=0; i<len-1; i++) {
int flag = 0;
for (int j=0; j<len-1-i; j++) {
if (a[j]>a[j+1]) {
swap(&a[j], &a[j+1]);
flag = 1;
}
}
if (!flag) break;
}
}
//折半-插入排序
void Insert_zheban(int a[], int n) {
int i,j,low = 0,high = 0,mid;
int temp = 0;
for (i=1; i<n; i++) {
low=0;
high=i-1;
temp=a[i];
//采用折半查找法判断插入位置,最终变量 low 表示插入位置
while (low<=high) {
mid=(low+high)/2;
if (a[mid]>temp) {
high=mid-1;
}else{
low=mid+1;
}
}
//有序表中插入位置后的元素统一后移
for (j=i; j>low; j--) {
a[j]=a[j-1];
}
a[low]=temp;//插入元素
}
}
//插入排序
void Insert_sort(int a[], int N) {
int tmp,j;
for (int i=1; i<N; i++) {
tmp = a[i];
for (j=i; j>=1&&a[j-1]>tmp; j--)
a[j] = a[j-1];
a[j] = tmp;
}
}
//基础的希尔排序
void Shell_sort(int a[], int N) {
int i,j,tmp;
for (int D=N/2; D>0; D/=2) {
for (i=D; i<N; i++) {
tmp = a[i];
for (j=i; j>=D&&a[j-D]>tmp; j-=D)
a[j] = a[j-D];
a[j] = tmp;
}
}
}
// Hibbard增量序列希尔排序
void Hibbard_shell_sort(int *A, int N) {
int add[]= {32767,16383,8191,4095,2047,1023,511,255,127,63,31,15,7,3,1,0};
int i=0;
for(int D=add[i]; D>0; D=add[++i]) {
for(int p=D; p<N; p++) {
int tmp = A[p];
int k=p;
for(; k>=D && tmp<A[k-D] ; k-=D) // j>=D 在前,因为也许 A[j-D]已经越界
A[k] = A[k-D];
A[k] = tmp;
}
}
}
// Sedgewick增量序列哈希排序
void Sedgewick_shell_sort(int *A, int N) {
int add[]= {587521,260609,146305,64769,36289,16001,8929,3905,2161,929,505,209,109,41,19,5,1,0};
int i=0;
for(int D=add[i]; D>0; D=add[++i]) {
for(int p=D; p<N; p++) {
int tmp = A[p];
int k = p;
for(; k>=D && tmp<A[k-D]; k-=D)
A[k] = A[k-D];
A[k] = tmp;
}
}
}
void Selection_sort(int *a, int N) {
int Minorder;
for (int i=0; i<N; i++) {
Minorder = ScanForMin(a, i, N);
swap(a[i], a[Minorder]);
}
}
int ScanForMin(int *a, int i, int N) {
int Order,min=1000000;
for(int n=i; n<N; n++) {
if (a[n]<min) {
min = a[n];
Order = n;
}
}
return Order;
}
void Heap_sort(int a[], int N) { //堆排序
int i,temp;
for (i=N/2; i>=0; i--)
adjust_node(a, i, N);//第一步是构造大根堆
for (i=N-1; i>0; i--) {
swap(&a[0], &a[i]);
adjust_node(a, 0, i);//不断输出堆顶的最大元素
}
}
void adjust_node(int *a, int i, int N) {
int l, r, Tmp, max;
l = 2 * i + 1; //左右孩子的索引,注意数组下标从0开始。
r = 2 * i + 2;
max = i;
if (l<N && a[l]>a[i])
max = l;
if (r<N && a[r]>a[max])
max = r;
if (max != i) {
swap(&a[i], &a[max]);
adjust_node(a, max, N); //保证最大堆
}
}
//归并排序
int b[10] = {0};
void merge(int *a, int L, int mid, int R) {
int i,j,m;
for (int k=L; k<=R; k++) {
b[k] = a[k];
}
for (i=L, j=mid+1, m=i; i<=mid&&j<=R; m++) {
if (b[i]<=b[j])
a[m] = b[i++];
else
a[m] = b[j++];
}
while (i<=mid) a[m++] = b[i++];
while (j<=R) a[m++] = b[j++];
}
void Merge_sort(int *a, int l, int r) { //在外排序的时候是一个比较有用的工具
/*
时间复杂度:O(nlogn)
空间复杂度:O(N),归并排序需要一个与原数组相同长度的数组做辅助来排序
稳定性:归并排序是稳定的排序算法
*/
if (l<r) {
int mid = (l+r)/2;
Merge_sort(a, l, mid);
Merge_sort(a, mid+1, r);
merge(a, l, mid, r);
}
}
//快排
int getpivot(int *a, int L, int R) { //快速排序选主元
int center = (L+R)/2;
if (a[R]<a[L]) swap(&a[R], &a[L]);
if (a[R]<a[center]) swap(&a[R], &a[center]);
if (a[center]<a[L]) swap(&a[center], &a[L]);
swap(&a[center], &a[R-1]);
return a[R-1];
}
void Quicksort(int *a, int left, int right) {
int cutoff = 100;
if (cutoff < right-left) {
int pivot = getpivot(a, left, right);
int i = left;
int j = right-1;
for(;;) /*for(;;)死循环里的两个;;代表两个空语句,编译器一般会优化掉它们,直接进入循环体。
while(1)死循环里的1被看成表达式,每循环一次都要判断常量1是不是等于零。
即,相对来说for式死循环更加高效一点*/
{
while(a[++i] < pivot);
while(a[--j] > pivot);
if (i<j) swap(&a[i], &a[j]);
else break;
}
swap(&a[i], &a[right-1]);
Quicksort(a, left, i-1);
Quicksort(a, i+1, right);
} else
Insert_sort(a+left, right-left+1);
}
/*或者**************************************/
void Quick_Sort(int *arr, int begin, int end){
if(begin > end)
return;
int tmp = arr[begin];
int i = begin;
int j = end;
while(i != j){
while(arr[j] >= tmp && j > i)
j--;
while(arr[i] <= tmp && j > i)
i++;
if(j > i){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
arr[begin] = arr[i];
arr[i] = tmp;
Quick_Sort(arr, begin, i-1);
Quick_Sort(arr, i+1, end);
}
/******************************************/
void Quick_sort(int *a, int N) {
Quicksort(a, 0 ,N-1);
}
//基数排序
void BaseSort(int *a, int N) {
//暂未完成
}
如果根据数据量选择对应排序算法?
数据0:只有1个元素;
数据量太少,没有差别。
数据1:11个不相同的整数,测试基本正确性;
数据量太少,没有差别。
数据2:10^3个随机整数;
插入排序最慢,理所当然;
shell排序竟然也慢,你知道吗? 是不是数据量太小看不出差距,或者这是一次随机的数据导致的?
数据3:10^4个随机整数;
最慢的是插入,理所当然,
数据4:10^5个随机整数;
插入排序拖后腿非常明显
性能最好的是segwick shell排序。
数据5:10^5个顺序整数;
使用的是快排,遇到随机的数据,碰到了最坏的情况n^2
插入排序竟然当第一啦。见解说明了生成的数据是接近有序的数据。
待优化快排(轴的选择,递归的区间选择,小区间和插入排序的结合等),
其他优化的方法:直接在输入的时候,识别是否是顺序或者逆序
数据6:10^5个逆序整数;
快排慢的原因见数据6,细心发现快排这里非常吃空间。
次之,插入排序也遇到了最坏的情况,竟然花销比快速排序少,个人认为递归的性能导致了本快排慢。
优化:在输入的时候,识别是否是或者逆序
数据7:10^5个基本有序的整数;
快速排序基础版依然慢,其他三个是一个数量级。
数据8:10^5个随机正整数,每个数字不超过1000。
Ps:其实这道题在10^5的数量级上上面可以非常自由的使用空间,用空间换时间
使用hash数组是最快的,
-----------数量级初步估算-----------
以上从数据层面分析了,下面初略量化分析了下花销:
以cpu 1Ghz位列,那么一秒执行10^9次,
n=10^5
n*logn=1151292.546497023 ~=11ms (初略计算,没有考虑n&logN的系数等)
所以基于比较的排序算法最优性能维持在 10ms数量级左右,综合上面的结果(表格最优的是20ms左右 10^5)一致。
-------纵向比较
以上从数据角度,数量级角度,我们还可以从算法角度,
没有优化的快速排序算法这里性能很差,下一周我们是不是要优化啊??o(^▽^)o
插入排序的表现在期望之中,适合在接近有序的条件下运行。
最后剩下堆排序和shell排序啦,这里可以看到segwick shell表现更出色,原因嘛,就是姥姥说的每次swap操作减少多个逆序对。