常見的幾種數組排序方法


一、研究數組排序的意義:

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

二、常見的數組排序方法:

以下研究,默認是對操作數組進行從小到大的排序。使用語言是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.寫在最后

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM