java八大排序算法

一、概述


? ? ? ?排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。

? ? ? ?我們這里說說八大排序就是內部排序。


? ? ? ?當n較大,則應采用時間復雜度為O(nlog2n)的排序方法:快速排序、堆排序或歸并排序序。

? ? ? ?快速排序:是目前基于比較的內部排序中被認為是最好的方法,當待排序的關鍵字是隨機分布時,快速排序的平均時間最短。

? ? ? ?下面我們來分別介紹這8中排序算法。


二、排序算法


1、插入排序——直接插入排序


使用場景:

? ? ? ?經常碰到這樣一類排序問題:把新的數據插入到已經排好的數據列中。


基本思想:

? ? ? ?1)將第一個數和第二個數排序,然后構成一個有序序列;

? ? ? ?2)將第三個數插入進去,構成一個新的有序序列;

? ? ? ?3)對第四個數、第五個數……直到最后一個數,重復第二步。


實現邏輯:

? ? ? ?1)首先設定插入次數,即循環次數,for(int i=1;i<length;i++),1個數的那次不用插入;

? ? ? ?2)設定插入數和得到已經排好序列的最后一個數的位數。insertNum和j=i-1;

? ? ? ?3)從最后一個數開始向前循環,如果插入數小于當前數,就將當前數向后移動一位;

? ? ? ?4)將當前數放置到空著的位置,即j+1。


代碼示例:

    public void insertSort(int[] a){int length=a.length;//數組長度,將這個提取出來是為了提高速度。int insertNum;//要插入的數for(int i=1;i<length;i++){//插入的次數insertNum=a[i];//要插入的數int j=i-1;//已經排序好的序列元素個數while(j>=0&&a[j]>insertNum){//序列從后到前循環,將大于insertNum的數向后移動一格a[j+1]=a[j];//元素移動一格j--;}a[j+1]=insertNum;//將需要插入的數放在要插入的位置。}}


2、插入排序——希爾排序


使用場景:

? ? ? ?對于直接插入排序問題,數據量巨大時。


基本思想:

? ? ? ?1)將數的個數設為n,取奇數k=n/2,將下標差值為k的書分為一組,構成有序序列;

? ? ? ?2)再取k=k/2 ,將下標差值為k的書分為一組,構成有序序列;

? ? ? ?3)重復第二步,直到k=1執行簡單插入排序。



實現邏輯:

? ? ? ?1)首先確定分的組數。

? ? ? ?2)然后對組中元素進行插入排序。

? ? ? ?3)然后將length/2,重復1,2步,直到length=0為止。


代碼示例:

    public  void sheelSort(int[] a){int d  = a.length;while (d!=0) {d=d/2;for (int x = 0; x < d; x++) {//分的組數for (int i = x + d; i < a.length; i += d) {//組中的元素,從第二個數開始int j = i - d;//j為有序序列最后一位的位數int temp = a[i];//要插入的元素for (; j >= 0 && temp < a[j]; j -= d) {//從后往前遍歷。a[j + d] = a[j];//向后移動d位}a[j + d] = temp;}}}}

3、選擇排序——簡單選擇排序


使用場景:

? ? ? ?常用于取序列中最大最小的幾個數時。


基本思想:

? ? ? ?如果每次比較都交換,那么就是交換排序;如果每次比較完一個循環再交換,就是簡單選擇排序。

? ? ? ?1)在要排序的一組數中,選出最小(或者最大)的個數與第1個位置的數交換;

? ? ? ?2)然后在剩下的數當中再找最小(或者最大)的與第2個位置的數交換;

? ? ? ?3)依次類推,直到第n-1個元素(倒數第二個數)和第n個元素(最后個數)比較為止。




實現邏輯:

? ? ? ?1)首先確定循環次數,并且記住當前數字和當前位置;

? ? ? ?2)將當前位置后面所有的數與當前數字進行對比,小數賦值給key,并記住小數的位置;

? ? ? ?3)比對完成后,將最小的值與第一個數的值交換;

? ? ? ?4)重復2、3步。


代碼示例:

    public void selectSort(int[] a) {int length = a.length;for (int i = 0; i < length; i++) {//循環次數int key = a[i];int position=i;for (int j = i + 1; j < length; j++) {//選出最小的值和位置if (a[j] < key) {key = a[j];position = j;}}a[position]=a[i];//交換位置a[i]=key;}}

簡單選擇排序的改進——二元選擇排序


? ? ? ?簡單選擇排序,每趟循環只能確定一個元素排序后的定位。我們可以考慮改進為每趟循環確定兩個元素(當前趟最大和最小記錄)的位置,從而減少排序所需的循環次數。改進后對n個數據進行排序,最多只需進行[n/2]趟循環即可。


代碼示例:

void SelectSort(int r[],int n) {  int i ,j , min ,max, tmp;  for (i=1 ;i <= n/2;i++) {    // 做不超過n/2趟選擇排序   min = i; max = i ; //分別記錄最大和最小關鍵字記錄位置  for (j= i+1; j<= n-i; j++) {  if (r[j] > r[max]) {   max = j ; continue ;   }    if (r[j]< r[min]) {   min = j ;   }     }    //該交換操作還可分情況討論以提高效率  tmp = r[i-1]; r[i-1] = r[min]; r[min] = tmp;  tmp = r[n-i]; r[n-i] = r[max]; r[max] = tmp;   }   
}  

4、選擇排序——堆排序


? ? ? ?堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。


使用場景:

? ? ? ?對簡單選擇排序的優化。


基本思想:

? ? ? ?堆的定義如下:具有n個元素的序列(k1,k2,...,kn),當且僅當滿足


時稱之為堆。由堆的定義可以看出,堆頂元素(即第一個元素)必為最小項(小頂堆)。
? ? ? ?若以一維數組存儲一個堆,則堆對應一棵完全二叉樹,且所有非葉結點的值均不大于(或不小于)其子女的值,根結點(堆頂元素)的值是最小(或最大)的。如:

? ? ? ?(a)大頂堆序列:(96, 83,27,38,11,09)

? ? ? ?(b)小頂堆序列:(12,36,24,85,47,30,53,91)


? ? ? ?初始時把要排序的n個數的序列看作是一棵順序存儲的二叉樹(一維數組存儲二叉樹),調整它們的存儲序,使之成為一個堆,將堆頂元素輸出,得到n 個元素中最小(或最大)的元素,這時堆的根節點的數最小(或者最大)。然后對前面(n-1)個元素重新調整使之成為堆,輸出堆頂元素,得到n 個元素中次小(或次大)的元素。依此類推,直到只有兩個節點的堆,并對它們作交換,最后得到有n個節點的有序序列。稱這個過程為堆排序

? ? ? ?因此,實現堆排序需解決兩個問題:
? ? ? ?1、如何將n 個待排序的數建成堆;
? ? ? ?2、輸出堆頂元素后,怎樣調整剩余n-1 個元素,使其成為一個新堆。


首先討論第二個問題:輸出堆頂元素后,對剩余n-1元素重新建成堆的調整過程。
調整小頂堆的方法:

? ? ? ?1)設有m 個元素的堆,輸出堆頂元素后,剩下m-1 個元素。將堆底元素送入堆頂((最后一個元素與堆頂進行交換),堆被破壞,其原因僅是根結點不滿足堆的性質。

? ? ? ?2)將根結點與左、右子樹中較小元素的進行交換。

? ? ? ?3)若與左子樹交換:如果左子樹堆被破壞,即左子樹的根結點不滿足堆的性質,則重復方法 (2).

? ? ? ?4)若與右子樹交換,如果右子樹堆被破壞,即右子樹的根結點不滿足堆的性質。則重復方法 (2).

? ? ? ?5)繼續對不滿足堆性質的子樹進行上述交換操作,直到葉子結點,堆被建成。

稱這個自根結點到葉子結點的調整過程為篩選。如圖:

再討論對n 個元素初始建堆的過程。
建堆方法:對初始序列建堆的過程,就是一個反復進行篩選的過程。

? ? ? ?1)n 個結點的完全二叉樹,則最后一個結點是第個結點的子樹。

? ? ? ?2)篩選從第個結點為根的子樹開始,該子樹成為堆。

? ? ? ?3)之后向前依次對各結點為根的子樹進行篩選,使之成為堆,直到根結點。

如圖建堆初始過程:無序序列:(49,38,65,97,76,13,27,49)




具體步驟:

? ? ? ?1)將序列構建成大頂堆;

? ? ? ?2)將根節點與最后一個節點交換,然后斷開最后一個節點;

? ? ? ?3)重復第一、二步,直到所有節點斷開。




代碼示例:

    public  void heapSort(int[] a){System.out.println("開始排序");int arrayLength=a.length;//循環建堆  for(int i=0;i<arrayLength-1;i++){//建堆  buildMaxHeap(a,arrayLength-1-i);//交換堆頂和最后一個元素  swap(a,0,arrayLength-1-i);System.out.println(Arrays.toString(a));}}private  void swap(int[] data, int i, int j) {// TODO Auto-generated method stub  int tmp=data[i];data[i]=data[j];data[j]=tmp;}//對data數組從0到lastIndex建大頂堆  private void buildMaxHeap(int[] data, int lastIndex) {// TODO Auto-generated method stub  //從lastIndex處節點(最后一個節點)的父節點開始  for(int i=(lastIndex-1)/2;i>=0;i--){//k保存正在判斷的節點  int k=i;//如果當前k節點的子節點存在  while(k*2+1<=lastIndex){//k節點的左子節點的索引  int biggerIndex=2*k+1;//如果biggerIndex小于lastIndex,即biggerIndex+1代表的k節點的右子節點存在  if(biggerIndex<lastIndex){//若果右子節點的值較大  if(data[biggerIndex]<data[biggerIndex+1]){//biggerIndex總是記錄較大子節點的索引  biggerIndex++;}}//如果k節點的值小于其較大的子節點的值  if(data[k]<data[biggerIndex]){//交換他們  swap(data,k,biggerIndex);//將biggerIndex賦予k,開始while循環的下一次循環,重新保證k節點的值大于其左右子節點的值  k=biggerIndex;}else{break;}}}}


5、交換排序——冒泡排序


使用場景:

? ? ? ?一般不用。


基本思想:

? ? ? ?1)將序列中所有元素兩兩比較,將最大的放在最后面;

? ? ? ?2)將剩余序列中所有元素兩兩比較,將最大的放在最后面;

? ? ? ?3)重復第二步,直到只剩下一個數。



實現邏輯:

? ? ? ?1)設置循環次數;

? ? ? ?2)設置開始比較的位數,和結束的位數;

? ? ? ?3)兩兩比較,將最小的放到前面去;

? ? ? ?4)重復2、3步,直到循環次數完畢。


代碼示例:

    public void bubbleSort(int[] a){int length=a.length;int temp;for(int i=0;i<a.length;i++){for(int j=0;j<a.length-i-1;j++){if(a[j]>a[j+1]){temp=a[j];a[j]=a[j+1];a[j+1]=temp;}}}}


冒泡排序算法的改進


? ? ? ?對冒泡排序常見的改進方法是加入一標志性變量exchange,用于標志某一趟排序過程中是否有數據交換,如果進行某一趟排序時并沒有進行數據交換,則說明數據已經按要求排列好,可立即結束排序,避免不必要的比較過程。本文再提供以下兩種改進算法:

? ? ? ?1、設置一標志性變量pos,用于記錄每趟排序中最后一次進行交換的位置。由于pos位置之后的記錄均已交換到位,故在進行下一趟排序時只要掃描到pos位置即可。


代碼示例:

void Bubble_1 ( int r[], int n) {  int i= n -1;  //初始時,最后位置保持不變  while ( i> 0) {   int pos= 0; //每趟開始時,無記錄交換  for (int j= 0; j< i; j++)  if (r[j]> r[j+1]) {  pos= j; //記錄交換的位置   int tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;  }   i= pos; //為下一趟排序作準備  }   
}  
? ? ? ? 2、傳統冒泡排序中每一趟排序操作只能找到一個最大值或最小值,我們考慮利用在每趟排序中進行正向和反向兩遍冒泡的方法一次可以得到兩個最終值(最大者和最小者) ,從而使排序趟數幾乎減少了一半。

代碼示例:

void Bubble_2 ( int r[], int n){  int low = 0;   int high= n -1; //設置變量的初始值  int tmp,j;  while (low < high) {  for (j= low; j< high; ++j) //正向冒泡,找到最大者  if (r[j]> r[j+1]) {  tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;  }   --high;                 //修改high值, 前移一位  for ( j=high; j>low; --j) //反向冒泡,找到最小者  if (r[j]<r[j-1]) {  tmp = r[j]; r[j]=r[j-1];r[j-1]=tmp;  }  ++low;                  //修改low值,后移一位  }   
}   


6、交換排序——快速排序


使用場景:

? ? ? ?要求時間最快時。


基本思想:

? ? ? ?1)選擇一個基準元素,通常選擇第一個元素或者最后一個元素,

? ? ? ?2)通過一趟排序講待排序的記錄分割成獨立的兩部分,其中一部分記錄的元素值均比基準元素值小。另一部分記錄的?元素值比基準值大。

? ? ? ?3)此時基準元素在其排好序后的正確位置

? ? ? ?4)然后分別對這兩部分記錄用同樣的方法繼續進行排序,直到整個序列有序。

一趟排序的過程:


排序的全過程:



代碼示例:

public static void quickSort(int[] numbers, int start, int end) {   if (start < end) {   int base = numbers[start]; // 選定的基準值(第一個數值作為基準值)   int temp; // 記錄臨時中間值   int i = start, j = end;   do {   while ((numbers[i] < base) && (i < end))   i++;   while ((numbers[j] > base) && (j > start))   j--;   if (i <= j) {   temp = numbers[i];   numbers[i] = numbers[j];   numbers[j] = temp;   i++;   j--;   }   } while (i <= j);   if (start < j)   quickSort(numbers, start, j);   if (end > i)   quickSort(numbers, i, end);   }   
}


分析:

? ? ? ?快速排序是通常被認為在同數量級(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按關鍵碼有序或基本有序時,快排序反而蛻化為冒泡排序。為改進之,通常以“三者取中法”來選取基準記錄,即將排序區間的兩個端點與中點三個記錄關鍵碼居中的調整為支點記錄。快速排序是一個不穩定的排序方法。


快速排序的改進


? ? ? ?在本改進算法中,只對長度大于k的子序列遞歸調用快速排序,讓原序列基本有序,然后再對整個基本有序序列用插入排序算法排序。實踐證明,改進后的算法時間復雜度有所降低,且當k取值為?8?左右時,改進算法的性能最佳。


代碼示例:

void print(int a[], int n){  for(int j= 0; j<n; j++){  cout<<a[j] <<"  ";  }  cout<<endl;  
}  void swap(int *a, int *b)  
{  int tmp = *a;  *a = *b;  *b = tmp;  
}  int partition(int a[], int low, int high)  
{  int privotKey = a[low];                 //基準元素  while(low < high){                   //從表的兩端交替地向中間掃描  while(low < high  && a[high] >= privotKey) --high; //從high 所指位置向前搜索,至多到low+1 位置。將比基準元素小的交換到低端  swap(&a[low], &a[high]);  while(low < high  && a[low] <= privotKey ) ++low;  swap(&a[low], &a[high]);  }  print(a,10);  return low;  
}  void qsort_improve(int r[ ],int low,int high, int k){  if( high -low > k ) { //長度大于k時遞歸, k為指定的數  int pivot = partition(r, low, high); // 調用的Partition算法保持不變  qsort_improve(r, low, pivot - 1,k);  qsort_improve(r, pivot + 1, high,k);  }   
}   
void quickSort(int r[], int n, int k){  qsort_improve(r,0,n,k);//先調用改進算法Qsort使之基本有序  //再用插入排序對基本有序序列排序  for(int i=1; i<=n;i ++){  int tmp = r[i];   int j=i-1;  while(tmp < r[j]){  r[j+1]=r[j]; j=j-1;   }  r[j+1] = tmp;  }   }   int main(){  int a[10] = {3,1,5,7,2,4,9,6,10,8};  cout<<"初始值:";  print(a,10);  quickSort(a,9,4);  cout<<"結果:";  print(a,10);  }

7、歸并排序


使用場景:

? ? ? ?速度僅次于快排,內存少的時候使用,可以進行并行計算的時候使用。


基本思想:

? ? ? ?1)選擇相鄰兩個數組成一個有序序列;

? ? ? ?2)選擇相鄰的兩個有序序列組成一個有序序列;

? ? ? ?3)重復第二步,直到全部組成一個有序序列。



實現邏輯:

設r[i…n]由兩個有序子表r[i…m]和r[m+1…n]組成,兩個子表長度分別為n-i +1、n-m。

? ? ? ?1、j=m+1;k=i;i=i; //置兩個子表的起始下標及輔助數組的起始下標;

? ? ? ?2、若i>m 或j>n,轉⑷ //其中一個子表已合并完,比較選取結束;

? ? ? ?3、//選取r[i]和r[j]較小的存入輔助數組rf

? ? ? ? ? ? ?如果r[i]<r[j],rf[k]=r[i]; i++; k++; 轉⑵

? ? ? ? ? ? ?否則,rf[k]=r[j]; j++; k++; 轉⑵

? ? ? ?4、//將尚未處理完的子表中元素存入rf

? ? ? ? ? ? ?如果i<=m,將r[i…m]存入rf[k…n] //前一子表非空

? ? ? ? ? ? ?如果j<=n , ?將r[j…n] 存入rf[k…n] //后一子表非空

? ? ? ?5、合并結束。??


代碼示例:

public static void mergeSort(int[] numbers, int left, int right) {   int t = 1;// 每組元素個數   int size = right - left + 1;   while (t < size) {   int s = t;// 本次循環每組元素個數   t = 2 * s;   int i = left;   while (i + (t - 1) < size) {   merge(numbers, i, i + (s - 1), i + (t - 1));   i += t;   }   if (i + (s - 1) < right)   merge(numbers, i, i + (s - 1), right);   }   
}   
private static void merge(int[] data, int p, int q, int r) {   int[] B = new int[data.length];   int s = p;   int t = q + 1;   int k = p;   while (s <= q && t <= r) {   if (data[s] <= data[t]) {   B[k] = data[s];   s++;   } else {   B[k] = data[t];   t++;   }   k++;   }   if (s == q + 1)   B[k++] = data[t++];   else  B[k++] = data[s++];   for (int i = p; i <= r; i++)   data[i] = B[i];   
}


歸并的迭代算法


? ? ? ?1 個元素的表總是有序的。所以對n 個元素的待排序列,每個元素可看成1 個有序子表。對子表兩兩合并生成n/2個子表,所得子表除最后一個子表長度可能為1 外,其余子表長度均為2。再進行兩兩合并,直到生成n 個元素按關鍵碼有序的表。


代碼示例:

void print(int a[], int n){  for(int j= 0; j<n; j++){  cout<<a[j] <<"  ";  }  cout<<endl;  
}  //將r[i…m]和r[m +1 …n]歸并到輔助數組rf[i…n]  
void Merge(ElemType *r,ElemType *rf, int i, int m, int n)  
{  int j,k;  for(j=m+1,k=i; i<=m && j <=n ; ++k){  if(r[j] < r[i]) rf[k] = r[j++];  else rf[k] = r[i++];  }  while(i <= m)  rf[k++] = r[i++];  while(j <= n)  rf[k++] = r[j++];  print(rf,n+1);  
}  void MergeSort(ElemType *r, ElemType *rf, int lenght)  
{   int len = 1;  ElemType *q = r ;  ElemType *tmp ;  while(len < lenght) {  int s = len;  len = 2 * s ;  int i = 0;  while(i+ len <lenght){  Merge(q, rf,  i, i+ s-1, i+ len-1 ); //對等長的兩個子表合并  i = i+ len;  }  if(i + s < lenght){  Merge(q, rf,  i, i+ s -1, lenght -1); //對不等長的兩個子表合并  }  tmp = q; q = rf; rf = tmp; //交換q,rf,以保證下一趟歸并時,仍從q 歸并到rf  }  
}  int main(){  int a[10] = {3,1,5,7,2,4,9,6,10,8};  int b[10];  MergeSort(a, b, 10);  print(b,10);  cout<<"結果:";  print(a,10);  }  


兩路歸并的迭代算法

代碼示例:

void MSort(ElemType *r, ElemType *rf,int s, int t)  
{   ElemType *rf2;  if(s==t) r[s] = rf[s];  else  {   int m=(s+t)/2;          /*平分*p 表*/  MSort(r, rf2, s, m);        /*遞歸地將p[s…m]歸并為有序的p2[s…m]*/  MSort(r, rf2, m+1, t);      /*遞歸地將p[m+1…t]歸并為有序的p2[m+1…t]*/  Merge(rf2, rf, s, m+1,t);   /*將p2[s…m]和p2[m+1…t]歸并到p1[s…t]*/  }  
}  
void MergeSort_recursive(ElemType *r, ElemType *rf, int n)  
{   /*對順序表*p 作歸并排序*/  MSort(r, rf,0, n-1);  
} 

8、基數排序


使用場景:

? ? ? ?用于大量數,很長的數進行排序時。


基本思想:

? ? ? ?1)將所有的數的個位數取出,按照個位數進行排序,構成一個序列;

? ? ? ?2)將新構成的所有的數的十位數取出,按照十位數進行排序,構成一個序列。



代碼示例:

    public void sort(int[] array) {//首先確定排序的趟數;     int max = array[0];for (int i = 1; i < array.length; i++) {if (array[i] > max) {max = array[i];}}int time = 0;//判斷位數;     while (max > 0) {max /= 10;time++;}//建立10個隊列;     List<ArrayList> queue = new ArrayList<ArrayList>();for (int i = 0; i < 10; i++) {ArrayList<Integer> queue1 = new ArrayList<Integer>();queue.add(queue1);}//進行time次分配和收集;     for (int i = 0; i < time; i++) {//分配數組元素;     for (int j = 0; j < array.length; j++) {//得到數字的第time+1位數;   int x = array[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);ArrayList<Integer> queue2 = queue.get(x);queue2.add(array[j]);queue.set(x, queue2);}int count = 0;//元素計數器;     //收集隊列元素;     for (int k = 0; k < 10; k++) {while (queue.get(k).size() > 0) {ArrayList<Integer> queue3 = queue.get(k);array[count] = queue3.get(0);queue3.remove(0);count++;}}}}


三、總結


各種排序的穩定性,時間復雜度和空間復雜度總結:



我們比較時間復雜度函數的情況:



時間復雜度函數O(n)的增長情況:



? ? ? ?所以對n較大的排序記錄。一般的選擇都是時間復雜度為O(nlog2n)的排序方法。


時間復雜度來說


(1)平方階(O(n2))排序

? ? ? ?各類簡單排序:直接插入、直接選擇和冒泡排序;
(2)線性對數階(O(nlog2n))排序
? ? ? ?快速排序、堆排序和歸并排序;
(3)O(n1+§))排序,§是介于0和1之間的常數。

? ? ? ?希爾排序

(4)線性階(O(n))排序

? ? ? ?基數排序,此外還有桶、箱排序。

說明:

? ? ? ?1)當原表有序或基本有序時,直接插入排序和冒泡排序將大大減少比較次數和移動記錄的次數,時間復雜度可降至O(n);

? ? ? ?2)而快速排序則相反,當原表基本有序時,將蛻化為冒泡排序,時間復雜度提高為O(n2);

? ? ? ?3)原表是否有序,對簡單選擇排序、堆排序、歸并排序和基數排序的時間復雜度影響不大。


穩定性


? ? ? ?排序算法的穩定性:若待排序的序列中,存在多個具有相同關鍵字的記錄,經過排序, 這些記錄的相對次序保持不變,則稱該算法是穩定的;若經排序后,記錄的相對 次序發生了改變,則稱該算法是不穩定的。?
? ? ? ?穩定性的好處:排序算法如果是穩定的,那么從一個鍵上排序,然后再從另一個鍵上排序,第一個鍵排序的結果可以為第二個鍵排序所用。基數排序就是這樣,先按低位排序,逐次按高位排序,低位相同的元素其順序再高位也相同時是不會改變的。另外,如果排序算法穩定,可以避免多余的比較。

? ? ? ?穩定的排序算法:冒泡排序、插入排序、歸并排序和基數排序。

? ? ? ?不是穩定的排序算法:選擇排序、快速排序、希爾排序、堆排序。


選擇排序算法的準則


? ? ? ?每種排序算法都各有優缺點。因此,在實用時需根據不同情況適當選用,甚至可以將多種方法結合起來使用。

選擇排序算法的依據

? ? ? ?影響排序的因素有很多,平均時間復雜度低的算法并不一定就是最優的。相反,有時平均時間復雜度高的算法可能更適合某些特殊情況。同時,選擇算法時還得考慮它的可讀性,以利于軟件的維護。一般而言,需要考慮的因素有以下四點:

? ? ? ?1、待排序的記錄數目n的大小;

? ? ? ?2、記錄本身數據量的大小,也就是記錄中除關鍵字外的其他信息量的大小;

? ? ? ?3、關鍵字的結構及其分布情況;

? ? ? ?4、對排序穩定性的要求。

設待排序元素的個數為n。

? ? ? ?1)當n較大,則應采用時間復雜度為O(nlog2n)的排序方法:快速排序、堆排序或歸并排序序。

? ? ? ? ? ? ? 快速排序:是目前基于比較的內部排序中被認為是最好的方法,當待排序的關鍵字是隨機分布時,快速排序的平均時間最短;
? ? ? ? ? ? ??堆排序:如果內存空間允許且要求穩定性的,

? ? ? ? ? ? ? 歸并排序:它有一定數量的數據移動,所以我們可能過與插入排序組合,先獲得一定長度的序列,然后再合并,在效率上將有所提高。

? ? ? ?2)當n較大,內存空間允許,且要求穩定性 =》歸并排序

? ? ? ?3)當n較小,可采用直接插入或直接選擇排序。

? ? ? ? ? ? ? 直接插入排序:當元素分布有序,直接插入排序將大大減少比較次數和移動記錄的次數。

? ? ? ? ? ? ? 直接選擇排序:元素分布有序,如果不要求穩定性,選擇直接選擇排序

? ? ? ?5)一般不使用或不直接使用傳統的冒泡排序。

? ? ? ?6)基數排序
? ? ? ? ? ? ? 基數排序是一種穩定的排序算法,但有一定的局限性:
? ? ? ? ? ? ? 1、關鍵字可分解。
? ? ? ? ? ? ? 2、記錄的關鍵字位數較少,如果密集更好
? ? ? ? ? ? ? 3、如果是數字時,最好是無符號的,否則將增加相應的映射復雜度,可先將其正負分開排序。



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446986.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446986.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446986.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

密鑰安全性討論之密鑰分層管理結構

密鑰分層管理結構 密鑰的安全管理通常采用層次化的保護方法。密鑰管理分層管理機制將密鑰分為三層&#xff0c;即根密鑰、密鑰加密密鑰和工作密鑰下層密鑰為上層密鑰提供加密保護&#xff0c;采用分層的密鑰結構有助于密鑰的管理滿足本規范的要求 工作密鑰 工作密鑰對本地保存…

windows安裝 Git Large File Storage大文件下載工具ge

下載地址 導航到 git-lfs.github.com 并單擊Download開始下載git-lfs的用法指南 驗證安裝成功 打開Git Bash驗證安裝成功&#xff0c;使用命令 git lfs install &#xff0c;如果出現 >Git LFS initlized&#xff0c;就代表安裝成功參考鏈接 安裝 Git Large File Storag…

Java基礎——volatile關鍵字解析

簡介volatile關鍵字雖然從字面上理解起來比較簡單&#xff0c;但是要用好不是一件容易的事情。由于volatile關鍵字是與Java的內存模型有關的&#xff0c;因此在講述volatile關鍵之前&#xff0c;我們先來了解一下與內存模型相關的概念和知識&#xff0c;然后分析了volatile關鍵…

Linux ubuntu對于cmake的版本更新

問題產生 在ubuntu環境下運行C代碼&#xff0c;工程文件中CMakeLists文件顯示要求cmake的版本最低是3.15&#xff0c;但是我的本地版本是3.11&#xff0c;雖然修改CMakelists文件為3.11也是可以編譯通過&#xff0c;但是潛在的問題是未知的。 查看本地cmake的版本 cmake --ve…

Java基礎——Java IO詳解

一、概述 1、Java IO Java IO即Java 輸入輸出系統。不管我們編寫何種應用&#xff0c;都難免和各種輸入輸出相關的媒介打交道&#xff0c;其實和媒介進行IO的過程是十分復雜的&#xff0c;這要考慮的因素特別多&#xff0c;比如我們要考慮和哪種媒介進行IO&#xff08;文件、控…

Java基礎——Java NIO詳解(二)

一、簡介 在我的上一篇文章Java NIO詳解&#xff08;一&#xff09;中介紹了關于標準輸入輸出NIO相關知識&#xff0c; 本篇將重點介紹基于網絡編程NIO&#xff08;異步IO&#xff09;。 二、異步IO 異步 I/O 是一種沒有阻塞地讀寫數據的方法。通常&#xff0c;在代碼進行 rea…

Java基礎——Java NIO詳解(一)

一、基本概念 1、I/0簡介 I/O即輸入輸出&#xff0c;是計算機與外界世界的一個借口。IO操作的實際主題是操作系統。在java編程中&#xff0c;一般使用流的方式來處理IO&#xff0c;所有的IO都被視作是單個字節的移動&#xff0c;通過stream對象一次移動一個字節。流IO負責把對象…

MAC上Git安裝與GitHub基本使用

參考鏈接 MAC上Git安裝與GitHub基本使用

Java基礎——深入理解Java線程池

簡介 我們使用線程的時候就去創建一個線程&#xff0c;這樣實現起來非常簡便&#xff0c;但是就會有一個問題&#xff1a; 如果并發的線程數量很多&#xff0c;并且每個線程都是執行一個時間很短的任務就結束了&#xff0c;這樣頻繁創建線程就會大大降低系統的效率&#xff0c;…

密碼機項目安裝軟件時候出現的問題以及對應的解決辦法

Could NOT find Boost (missing: locale) (found version "1.65.1") 使用命令 apt-get install libboost-locale-dev 進行安裝 解決普通用戶cmake版本11&#xff0c;而root用戶版本15&#xff0c;clion對于版本兼容的問題 修改clion里面的toolchain&#xff0c;將其…

Java基礎——線程及并發機制

前言 在Java中&#xff0c;線程是一個很關鍵的名詞&#xff0c;也是很高頻使用的一種資源。那么它的概念是什么呢&#xff0c;是如何定義的&#xff0c;用法又有哪些呢&#xff1f;為何說Android里只有一個主線程呢&#xff0c;什么是工作線程呢。線程又存在并發&#xff0c;并…

密碼機 密鑰管理項目安裝配置 從零開始

安裝gcc 更新sudo apt-get update下載gcc sudo apt-get install gcc參考鏈接 不推薦 安裝g 下載g sudo apt-get install g 安裝make sudo apt -get install make參考鏈接 安裝cmake 下載地址參考鏈接 安裝ssh sudo apt-get install ssh 安裝git和配置 sudo apt-get inst…

Androud 如何有效減少重復代碼

前言 重復的代碼一直都是可維護性的大敵&#xff0c;重構的重要任務之一也就是要去除掉重復的代碼&#xff0c;有效的減少重復代碼&#xff0c;可以大大提高軟件的擴展性。 在Android開發中&#xff0c;很容易產生重復的代碼。因為Android是組件&#xff0c;模板式開發&#xf…

解決在sample文件夾里面寫代碼,在測試的時候因為virtual原因,make編譯報錯

代碼的結構 錯誤顯示 解決辦法 添加一句話&#xff0c;具體的cpp依據情況而定set_source_files_properties(${PROJECT_SOURCE_DIR}/src/sample_storage_test.cpp COMPILE_FLAGS "-Wno-unused-parameter")

Android SharedPreferences總結及優化

一、SharedPreferences簡介 Android 中的 SharedPreferences&#xff08;后續簡稱SP&#xff09;是輕量級的數據存儲方式&#xff0c;能夠保存簡單的數據類型&#xff0c;比如 String、int、boolean 值等。應用場合主要是數據比較少的配置信息。其內部是以 XML 結構保存在 /dat…

Java基礎——深入理解ReentrantLock

一、簡介在Java中通常實現鎖有兩種方式&#xff0c;一種是synchronized關鍵字&#xff0c;另一種是Lock。二者其實并沒有什么必然聯系&#xff0c;但是各有各的特點&#xff0c;在使用中可以進行取舍的使用。二、ReentrantLock與synchronized的比較相同點&#xff1a; &#xf…

使用開源的openssl的md5頭文件,實現對于文件的md5代碼

需要安裝openssl的庫 sudo apt-get install opensslsudo apt-get install libssl-dev參考鏈接 代碼 #include "openssl/md5.h" #include <iostream> #include <fstream> #include <iomanip>//#define MAX_DATA_BUFF 1024; //#define MD5_LENGTH…

Android 多進程開發

前言正常情況下&#xff0c;一個apk啟動后只會運行在一個進程中&#xff0c;其進程名為AndroidManifest.xml文件中指定的應用包名&#xff0c;所有的基本組件都會在這個進程中運行。但是如果需要將某些組件&#xff08;如Service、Activity等&#xff09;運行在單獨的進程中&am…

clion中鏈接openssl庫

錯誤顯示 前提條件 apt-get install opensslapt-get install openssl-dev 解決辦法 在CMakeLists.txt文件中加入如下命令link_libraries(crypto) 參考鏈接 無法將openssl庫鏈接到CLion C 程序c - 無法將openssl庫鏈接到CLion C程序