1. 程式人生 > >常見的幾種陣列排序方法

常見的幾種陣列排序方法

---恢復內容開始---

一、研究陣列排序的意義:

資料結構中,排序演算法各有用處,不同的排序方法有不同的時間複雜度與空間複雜度。為了能夠依據不同情況,選用不同的排序方法解決不同的問題。

二、常見的陣列排序方法:

以下研究,預設是對運算元組進行從小到大的排序。使用語言是Java。

1.選擇排序法

選擇排序法是將需要操作的陣列分為已排序部分和未排序部分兩部分。未排序的陣列元素中,最小(或最大)的元素依次按照獲得順序放入已排序的元素中。

public static boolean sortByChoice(int[] arr) {
   if(arr == null || arr.length == 0) {
       return false;
  }
   for (int i = 0; i < a.length; i++) {
       for (int j = i + 1; j < a.length; j++) {
           int swap = arr[j];
           if (swap < arr[i]) {
               arr[j] = arr[i];
               arr[i] = swap;
          }
      }
  }
   return true;
}

在上述的排序方法中,我們是直接交換了陣列中元素的值。那麼請看下面一段程式碼:

public static boolean sortIndexByChoice(int[] a) {
   if(a == null || a.length == 0) {
       return false;
  }
   for (int i = 0; i < a.length; i++) {
       int mark = i;
       //記錄需要交換的元素的下標
       for (int j = i + 1; j < a.length; j++) {
           if (a[mark] > a[j]) {
               mark = j;
          }
       //交換目標
       int swap = a[mark];
       a[mark] = a[j];
       a[j] = swap;
      }
  }
   return true;
}

上述方法中,通過定義指標變數mark指向(記錄)陣列中最小元素(的下標),直接交換指標指向的元素未排序部分首位的值進行交換。

選擇排序法的時間複雜度為O(n^2);最多交換次數為N-1次。

2.氣泡排序法(起泡排序法)

氣泡排序法是在每次迴圈排序過程中,每次交換需要交換的相鄰兩個元素。氣泡排序法雖然理論上,時間複雜度和選擇排序法相等,都是O(n^2),但是實際情況下,由於氣泡排序法較難避免重複比較,最壞情況下,程式執行比較的次數為n^n,消耗時間過長。

public static boolean bubbleSort(int[] a) {
if(a == null || a.length == 0) {
       return false;
  }
for (int i = 0; i < a.length-1;i++) {
for (int j = 1; j < a.length - i; j++) {
if (a[j-1] > a[j]) {
int temp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
}
      }
}
return true;
}

將陣列中相鄰元素多次進行各個比較,它存在一個問題,即使陣列已經有序,函式仍然在執行。因此,氣泡排序法可以嘗試進行優化。請看下一段程式碼:

public static boolean bubbleSort1(int[] a) {
if(a == null || a.length == 0) {
       return false;
  }
for (int i = 0; i < a.length-1;i++) {
boolean flag = false;
for (int j = 1; j < a.length - i; j++) {
if (a[j-1] > a[j]) {
int temp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
if(!flag) {
flag = true;
}
}
}
if(!flag) {
break;
}
}
return true;
}

相比起最開始的演算法,加入了一個標識變數flag,用於標識是當前迴圈是否進行過交換。如果當前迴圈未進行交換,則終止外圍迴圈。我們不難發現,每次迴圈後最大的數值會向後移動,這些元素被移動到陣列末位之後是不需要比較的,而其實多輪迴圈中,無論第一種演算法還是第二種演算法,這些元素仍然產生比較的過程。是否可以再次進行優化?請看下面一段程式碼:

public static boolean bubbleSort2(int[] a) {
   if(a == null || a.length == 0) {
      return false;
  }
   
   //定義下標變數endPos代表下一次迴圈中,最後一個需要比較的元素的下標。
   int endPos = a.length - 1;
   while(endPos > 0) {
       int flag = 1;
       for(int i = 1; j <= endPos; j++) {
           if (a[j-1] > a[j]) {
               int temp = a[j];
               a[j]= a[j-1];
               a[j-1] =temp;
               
               //由於該指標不斷被重新賦值,將該指標指向最後一位參與交換的元素下標。
               flag = j;
          }
      }
       //調整endPos,使下一次迴圈只對flag指向的元素前面的元素進行比較與排序。flag後面的元素已拍好。
       endPos = flag - 1;
  }
}

與bubbleSioer1相似的是,額外定義了一個指標變數flag,指向每次迴圈最後一次參與交換的陣列元素。因此,每次迴圈只對未被排序的元素進行比較。

接下來,請看下面一段程式碼:

public static boolean bubbleBothway(int[] arr) {
   if(arr == null || arr.length == 0) {
       return false;
  }
int high = arr.length - 1;
int low = 0;
while (low < high) {
for(int i = low ;  i < high;i++) {
if(arr[i] > arr[i+1]) {
int temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
}
}
high--;

for(int j= high; j > low;  j--) {
if(arr[j - 1] > arr [j]) {
       int swap = arr[j - 1];
       arr[j - 1] = arr[j];
       arr[j] = swap;
}
}
low++;
}
return true;
}

這種演算法是冒泡程式法的一種新的改進方法。相比於前面三種方法,它通過雙向比較,同時,規避了重複比較的情況,效率相對前面更高。

3.插入排序法

插入排序法是將未排序部分的首位元素抽取出來,插入至已排序元素的合適位置。

public static boolean insertSort(int[] arr) {
   if(arr == null || arr.length == 0) {
       return false;
  }
for(int i = 1; i < arr.length; i ++) {
int temp = arr[i];
int j = i - 1;
while (temp < arr[j]) {
arr[j + 1] = arr[j]; //後移前面的元素
j--;
if(j < 0) { //需要插入在首位時
break;
}
}
arr[j + 1] = temp; //插入元素
}
return true;
}

插入排序法理解起來相對簡單,時間複雜度同為O(n^2)。

4.希爾排序法

希爾排序法是插入排序法的一種改進。希爾排序法的基本思想是:將陣列分成若干個子序列,對於若干個子序列,進行插入排序,然後將這些序列進行插入排序。希爾排序法的核心問題就是在選擇序列上。這些序列是採取一定的增量資料組成一個序列,隨著排序過程,這個增量會減小。一般情況下,初始增量取陣列長度的一半,然後每次再進行折半,直到增量為1一般情況下,初始增量取陣列長度的一半,然後每次再進行折半,直到增量為1。請看下面一個例子:

假設說,下面有一個數組

    int arr[10]= {10, 9, 7, 2, 5, 6, 8, 4, 19, 1};

假設此次增量取5,那麼就有以下子序列

    {a[0] = 10, a[5] = 6}
{a[1] = 9, a[6] = 8}
{a[2] = 7, a[7] = 4}
{a[3] = 2, a[8] = 19}
{a[4] = 5, a[9] = 1}

然後,將每個增量通過排序後,得到下面的陣列:

    {6, 8, 4, 2, 1, 10, 9, 7, 19, 5}

然後,將增量折半,就有新的子序列,再將子序列插入排序:

    {6, 4, 1, 9, 19} ->{1, 4, 6, 9, 19}
{8, 2, 10, 7, 5} ->{2, 5, 7, 8, 10}

因此,得到新陣列

    {1, 2, 4, 5, 6, 7, 9, 8, 19, 10}

最後,對陣列直接進行一次插入排序

    {1, 2, 4, 5, 6, 7, 8, 9, 10, 19}

 

使用java實現希爾排序法的原始碼如下:

    public static boolean shellSort(int[] arr) {
   if(arr == null || arr.length == 0) {
       return false;
  }
   int h = arr.length / 2; //定義增量變數h
   while(h >= 1) {
   for (int i =h; i< arr.length; i++) {
  int j = i - h; //依據增量,開始分組。
  int temp = arr[i];
               
               /*子序列插入位置後的元素向後移動*/
  while(j>=0 && arr[j]>temp) {
  arr[j + h] = arr[j];
  j -= h;
  }

  arr[ j+h ] = temp; //移動完成,插入元素值
  }
   h  /= 2; //縮小增量
  }
   return true;
}

希爾排序法相對於插入排序法,減少了陣列中大量的元素移動的過程。希爾排序法思路和程式碼相對複雜。

5.快速排序法

快速排序法是最受到程式設計師歡迎的一種演算法。它的思想方法是將一個容量較大的陣列分成多個小陣列,然後將各個小陣列再次分成多個更小的陣列,直到元素達到一定值時,開始比較各個小陣列中的元素。

5.1遞迴法簡介

在討論快速排序法的問題之前,我們先說一下什麼是函式的遞迴法:

先看一下下面的數學問題:

已知等差數列的遞推公式a(n) = a(n-1) +2,其初始項a(1)=2,求其第18項a(18)。

的確,我們可以用求通用公式的方法求得結果。這裡,我們不求結果,先討論一下這個展開過程

這個數列求a(18)可以看做:

a(18)= a(18-1) + 2
=(a((18-1)-1)+2)+2
=((a(((18-1)-1)-1)+2)+2)+2
    ...

遞推套用遞推,直到找到基準值位置。其實這就是遞迴過程。

用java描述可以是:

public static int a(int x) {
   if (x = 1) {
       return 0;
  } else {
       return a(x - 1) + 2
  }
}

遞迴就是假定函式f(x),通過f(x)和f(ax + b)(a,b均為常數的關係,與當x=x0(x0為任意常數)時f(x)的值,求得給定任一x時f(x)的方法。f(x)=f(ax+b)+c為遞迴函式,f(x0)=f為基準。

5.2 快速排序法

現在,我們回到演算法分析上。快速排序法的原始碼如下:

    public static boolean fastSort(int[] arr, int left ,int right) {
   if(arr == null || arr.length == 0||left<0||right>arr.length) {
  System.out.println("傳參不合法!");
  return false;
  }
   if (left < right) {
   int s = arr[left];
   int i = left;
   int j = right +1;
   while(true) {
  //向右找大於s的元素下標
  //基本語法問題:由於++i已對i操作,這個while後面是一句空語句,用“;”隔開。
  while(i+1 < arr.length && arr[++i] < s) ;
  // 向左查詢小於s的元素值的下標
  while(j-1> - 1 &&arr[--j]>s);
  if(i >= j) {
  // 如果左標i大於或等於右標j,退出死迴圈
  break;
  } else {
  // 交換i與j的位置
  int temp = arr[i];
  arr[i] = arr[j];
  arr[j]= temp;
  }
  }
   arr[left] = arr[j];
   arr[j] = s;
   System.out.println(Arrays.toString(arr));
   //對左側陣列遞迴
   fastSort(arr, left, j-1);
  // 對右側陣列遞迴;
   fastSort(arr, j+1, right);
  }
   return true;
}

快速排序法的時間複雜度為O(n*log n),但是正因為遞迴呼叫,面對容量較大的陣列時,穩定性較差。

6.寫在最後

其實,陣列排序的方法還有很多,這裡,只討論一些比較基礎的,也是比較受歡迎的排序演算法。有興趣的話,可以去學習《資料結構》,瞭解更多關於排序的演算法。最後,本文會有很多不足之處,提前感謝各位對本文不足之處提出建議。謝謝大家。

---恢復內容結束---