下面是幾種常用的排序算法。插入排序,選擇排序,冒泡排序,堆排序,希爾排序,快速排序,歸併排序,基數排序。排序問題是對一個現有的隊伍,我們希望其中的元素以某種順序進行排列而產生的算法。我簡單的把他們分爲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;
}