下面是几种常用的排序算法。插入排序,选择排序,冒泡排序,堆排序,希尔排序,快速排序,归并排序,基数排序。排序问题是对一个现有的队伍,我们希望其中的元素以某种顺序进行排列而产生的算法。我简单的把他们分为2类。
1.基础排序算法
插入排序,选择排序,冒泡排序和希尔排序是基础排序算法,其时间复杂度都是n^2。其中插入和选择排序的过程如下图。
插入排序是假设当前的i已经有序(i从第一个开始),对于下面的元素j,我们把j插入到有序数组里的相应位置上得到的队伍也是有序的。
选择排序是从无序队伍里选择一个最小的排在最前面,继续执行后面的操作。这一过程保证了前面是有序的。
冒泡排序则相对简单粗暴一点,当初我大一的时候凭自己想到的也是这种排序。其结构是两重循环,思想是如果两个数无序(我们称之为无序对),就交换他们的位置,那么最后没有无序对的话,队伍就是有序的了。
希尔排序通过将数据分成不同的组,分组的依据如3的倍数,9的倍数等等。先对每一组进行排序,然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数。平均效率是O(nlogn)。它是一种不稳定的排序算法。
2.高级排序算法
归并排序是严格的时间复杂度为nlogn的算法。它是递归执行的,每次将现有无序队伍分成两半,递归执行得到有序队列后在进行合并操作。此算法的缺点是空间消耗量很大。
快速排序顾名思义是很快的排序,它运用了分治的思想,每次找一个基准值,把队伍分成比这个值大的和比这个值小的两个队伍,再递归的执行这一操作。最后得到了有序的队伍。这个算法由于卓越的时间和空间复杂度被广泛应用。虽然时间复杂度最坏是n^2,但是极少情况能达到,事实上,快速排序的效率是相当高的。
堆排序是运用二叉堆来实现的排序。在建立这个堆的时候就有了某种特定的顺序,最后调整的时候再不断地进行sink操作最后得到的就是一个有序的队伍了。另外,它是一种不稳定的排序算法。时间复杂度是nlogn。
3.另外
基数排序也是一种排序方法。
package DS;
import java.util.*;
public class AllSort {
static int min(int a,int b){
return a>b?b:a;
}
static int max(int a,int b){
return a>b?a:b;
}
static void charu(int a[],int n){
for(int i=1;i<n;i++){
for(int j=i;j>0&&a[j]<a[j-1];j--){
int t = a[j];
a[j]=a[j-1];
a[j-1]=t;
}
}
}
static void maopao(int a[],int n){
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(a[i]>a[j]) {
int t = a[i];
a[i]=a[j];
a[j]=t;
}
}
static void xuanze(int a[],int n){
for(int i=0;i<n;i++)
{
int mi=1<<31-1,m=i;
for(int j=i+1;j<n;j++)
if(a[j]<mi) {mi=a[j];m=j;}
int t = a[i];
a[i]=mi;
a[m]=t;
}
}
static void xir(int a[],int n) //希尔排序
{
int h=1;
while(h<n/3) h=3*h+1;//1,4,13,40,121,364,1093...
while(h>=1)
{
for(int i=h;i<n;i++)
{
for(int j=i;j>=h&&a[j]<a[j-h];j-=h)
{
int t= a[j];
a[j]=a[j-h];
a[j-h]=t;
}
}
h/=3;
}
}
static int[] b = new int[10000000];
static void guibing(int a[],int lo,int mid,int hi)
{
int i=lo;
int j=mid+1;
for(int k=lo;k<=hi;k++)
b[k]=a[k];
for(int k=lo;k<=hi;k++)
if(i>mid) a[k]=b[j++];
else if(j>hi) a[k]=b[i++];
else if(b[j]<b[i]) a[k]=b[j++];
else a[k]=b[i++];
}
static void guibingsort(int a[],int lo,int hi)//归并排序化整为零
{
if(hi<=lo) return ;
int mid=lo+(hi-lo)/2;
guibingsort(a,lo,mid);
guibingsort(a,mid+1,hi);
guibing(a,lo,mid,hi);
}
static void guibingxiadaoshang(int a[],int n)//归并排序归零为整
{
for(int sz=1;sz<n;sz+=sz)
for(int lo=0;lo<n-sz;lo+=sz+sz)
guibing(a,lo,lo+sz-1,min(lo+sz+sz-1,n-1));
}
static int qie(int a[],int lo,int hi){
int i=lo+1,j=hi;
int tem = a[lo];
while(i<=j){
while(i<=j&&a[j]>tem) j--;
while(i<=j&&a[i]<tem) i++;
if(i>=j) break;
int t=a[i];a[i]=a[j];a[j]=t;
}
int t = a[lo];a[lo]=a[j];a[j]=t;
return j;
}
static int qiefen(int a[],int lo,int hi)//快排切分函数
{
int i=lo;int j=hi+1;
int v = a[lo];
while(true)
{ while(v<a[--j]) if(j==lo) break;
while(a[++i]<v) if(i==hi) break;
if(i>=j) break;
int t=a[i];
a[i]=a[j];
a[j]=t;
}
int t = a[lo];
a[lo]=a[j];
a[j]=t;
return j;
}
static void quicksort(int a[],int lo,int hi)//快排
{
if(hi<=lo) return ;
int j=qie(a,lo,hi);
quicksort(a,lo,j-1);
quicksort(a,j+1,hi);
}
static void sink(int a[],int k,int n)//堆排序的sink操作
{
while(2*k<=n)
{
int j=2*k;
if(j<n && a[j]<a[j+1])j++;
if(a[k]>=a[j]) break;
int t = a[k];
a[k]=a[j];
a[j]=t;
k=j;
}
}
static void duipaixu(int a[],int n)//堆排序
{
for(int k=n/2;k>=1;k--)
sink(a,k,n); //初始化堆
while(n>1){ //开始搞事
int t=a[1];
a[1]=a[n];
a[n--]=t;
sink(a,1,n);
}
}
public static void main(String[] args) {
final int len=10000;
int[] te = new int[len];
Random re = new Random();
for(int i=0;i<len;i++)
te[i]=re.nextInt();
double sttim = System.currentTimeMillis();
xuanze(te,len);
double endt = System.currentTimeMillis();
/*for(int i=0;i<len;i++){
System.out.print(te[i]+" ");
}
*/
System.out.println(endt-sttim);
}
}
/* 排序结果如下
10000 100000 200000 500000 1000000 10000000
charu 48 , 4000 16057 384000
xuanze 27 2000 8238 193571
maopao 47 4378 19000 491793
duipa 7 18 61 71 147 2695
guibin 5 14 33 69 117 1608
quick 2 20 28 73 115 1206
xir 4 17 37 90 201 2905
*/
我们用java的Radom方法来生成数组进行测试,结果如上。最上方是测试规模。每个值都是我多次测试取中位数所得。
更新:基数排序
基数排序是O(n)的算法,也可以用n的空间来完成,但是数据局限性很大,一般用于位数较少的数的排序。后缀数组必用。
首先排序个位,在个位有序的基础上再排序十位,在十位和个位有序的情况下排序百位......最后在O(n)的复杂度下完成了排序。
下面的count数组记录的是数位,也就是0到9,的右限界。这就避免了重复赋值。
#include<iostream>
using namespace std;
#define MAXSIZE 10000
//获取一个数的长度
int length(int a){
int num = 0;
while(a){
a/=10;
num++;
}
return num;
}
//获取数组最长的数的长度
int maxLength(int ar[],int n) {
int malen = 0;
for (int i = 0; i < n; i++) {
int currentLength = length(ar[i]);
if (malen < currentLength) {
malen = currentLength;
}
}
return malen;
}
//获取x右往左从0开始的第d位数字
int getdigit(int x, int d) {
int a[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
return ((x / a[d]) % 10);
}
void lsdradix_sort(int arr[],int d,int n)
{
const int radix = 10;
int count[radix], i, j;
int *bucket = new int[n];
//按照分配标准依次进行排序过程
for(int k = 1; k <= d; ++k)
{
for(i = 0; i < radix; i++)
{
count[i] = 0;
}
//统计各个桶中所盛数据个数
for(i = 0; i < n; i++)
{
count[getdigit(arr[i], k)]++;
}
//count[i]表示第i个桶的右边界索引
for(i = 1; i < radix; i++)
{
count[i] = count[i] + count[i-1];
}
for(i = n-1;i >= 0; --i) //这里要从右向左扫描,保证排序稳定性
{
j = getdigit(arr[i], k);
bucket[count[j]-1] = arr[i];
/**
这里要注意的是,count的内容是不会重复的
因为在上一个循环中就已经规定了各位占多少位数了。
*/
--count[j];
}
for(i = 0,j = 0; i < n; ++i, ++j)
{
arr[i] = bucket[j];
}
}
delete [] bucket;
}
int main()
{
int br[10] = {789, 80, 90, 589, 998, 965, 852, 123, 456, 20};
int len = maxLength(br,10);
lsdradix_sort(br,len,3);
for(int i=0;i<10;i++)
cout<<br[i]<<" ";
cout<<endl;
return 0;
}