排序算法經過了很長時間的演變,產生了很多種不同的方法。對於初學者來說,對它們進行整理便於理解記憶顯得很重要。每種算法都有它特定的使用場合,很難通用。因此,我們很有必要對所有常見的排序算法進行歸納。
排序大的分類可以分爲兩種:內排序和外排序。在排序過程中,全部記錄存放在內存,則稱爲內排序,如果排序過程中需要使用外存,則稱爲外排序。下面講的排序都是屬於內排序。
內排序有可以分爲以下幾類:
(1)、插入排序:直接插入排序、二分法插入排序、希爾排序。
(2)、選擇排序:直接選擇排序、堆排序。
(3)、交換排序:冒泡排序、快速排序。
(4)、歸併排序
(5)、基數排序
1.插入排序
1.1直接插入排序
直接插入排序(Straight Insertion Sort)是一種最簡單的排序方法,其基本操作是將一條記錄插入到已排好的有序表中,從而得到一個新的、記錄數量增1的有序表。
算法中引進的附加記錄arr[i]稱監視哨或哨兵(Sentinel)。
哨兵有兩個作用:
① 進入查找(插入位置)循環之前,它保存了arr[i]的副本,使不致於因記錄後移而丟失arr[i]的內容;
② 它的主要作用是:在查找循環中"監視"下標變量l是否越界。一旦越界(即l=0),因爲arr[0]可以和自己比較,循環判定條件不成立使得查找循環結束,從而避免了在該循環內的每一次均要檢測l是否越界(即省略了循環判定條件"l>=1")。
例:
原有序表:(9 15 23 28 37) 20
找插入位置 : (9 15 ^ 23 28 37) 20
新有序表: (9 15 20 23 28 37)
public void insertSort(int[] arr){
//從第一個元素開始做爲插入元素,和前面排好序的元素比較找到插入位置
for(int i=1; i<arr.length; i++){
int temp = arr[i];
int l = i-1;
//在前面排序好的數組找到插入位置
for(; l>=0; l--){
if(arr[l]>temp){
arr[l+1] = arr[l]; //元素向後移動
}else{
break; //找到插入元素位置
}
}
arr[l+1] = temp; //插入元素
}
System.out.println(arr);
}
1.2二分法插入排序
二分法插入排序,簡稱二分排序,是在插入第i個元素時,對前面的0~i-1元素進行折半,先跟他們中間的那個元素比,如果小,則對前半再進行折半,否則對後半進行折半,直到left<right,然後再把第i個元素前1位與目標位置之間的所有元素後移,再把第i個元素放在目標位置上。
public void advanceInsertSortWithBinarySearch(int[] arr){
for(int i=1; i<arr.length; i++){
int temp = arr[i]; //要插入元素
int left = 0;
int right = i-1;
int mid = -1;
while (left<=right){
mid = left + (right - left)/2; //中間位置
//找到新的左側和右側座標
if(arr[mid]>temp){
right = mid - 1;
}else {
left = mid + 1;
}
}
for(int l=i-1; l>=left; l--){ //大於temp元素向右移動一位
arr[l+1] = arr[l];
}
arr[left] = temp; //插入元素
}
System.out.println(arr);
}
1.3希爾排序
希爾排序(Shell's Sort)是插入排序的一種又稱“縮小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一種更高效的改進版本。希爾排序是非穩定排序算法。該方法因D.L.Shell於1959年提出而得名。
希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止
給定實例的shell排序的排序過程
假設待排序文件有10個記錄,其關鍵字分別是:
49,38,65,97,76,13,27,49,55,04。
增量序列的取值依次爲:
5,2,1
public void xiersort(int[] arr){
int gap = arr.length;
while (true){
gap = gap/2; //分組,間隔gap的元素
for(int i=0; i<gap; i++){ //比較gap組數據
for(int l=i+gap; l<arr.length; l=+gap){ //執行插入排序,間隔gap的元素
int temp = arr[l];
int k = l-gap;
while (k>=0 && temp>arr[k]){ //將元素向前移動
arr[k+gap] = arr[k];
k = k-gap;
}
arr[k+gap] = temp; //插入元素
}
}
if(gap == 1) //當分組等於1,停止排序
break;
}
System.out.println(arr);
}
2.選擇排序
選擇排序(Selection sort)是一種簡單直觀的排序算法。它的工作原理是:第一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,然後再從剩餘的未排序元素中尋找到最小(大)元素,然後放到已排序的序列的末尾。以此類推,直到全部待排序的數據元素的個數爲零。選擇排序是不穩定的排序方法。
2.1直接選擇排序
選擇排序法的第一層循環從起始元素開始選到倒數第二個元素,主要是在每次進入的第二層循環之前,將外層循環的下標賦值給臨時變量,接下來的第二層循環中,如果發現有比這個最小位置處的元素更小的元素,則將那個更小的元素的下標賦給臨時變量,最後,在二層循環退出後,如果臨時變量改變,則說明,有比當前外層循環位置更小的元素,需要將這兩個元素交換.
public void selectSort(int[] arr){
for(int i=0; i<arr.length; i++){
int min = i; //最小元素位置臨時變量(0-arr.length-1)
for(int l=i+1; l<arr.length; l++){ //在未排序剩餘元素找到最小元素位置
if(arr[min]>arr[l]){
min = l; //找到最小元素位置
}
}
if(min != i){ //與最小位置元素替換,繼續尋找第二,第三個位置最小元素;
int value = arr[min];
arr[min] = arr[i];
arr[i] = value;
}
}
System.out.println(arr);
}
2.2堆排序
a.基本思想:
堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。
堆的定義下:具有n個元素的序列 (h1,h2,…,hn),當且僅當滿足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,…,n/2)時稱之爲堆。在這裏只討論滿足前者條件的堆。由堆的定義可以看出,堆頂元素(即第一個元素)必爲最大項(大頂堆)。完全二叉樹可以很直觀地表示堆的結構。堆頂爲根,其它爲左子樹、右子樹。
思想:初始時把要排序的數的序列看作是一棵順序存儲的二叉樹,調整它們的存儲序,使之成爲一個堆,這時堆的根節點的數最大。然後將根節點與堆的最後一個節點交換。然後對前面(n-1)個數重新調整使之成爲堆。依此類推,直到只有兩個節點的堆,並對它們作交換,最後得到有n個節點的有序序列。從算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最後一個元素交換位置。所以堆排序有兩個函數組成。一是建堆的滲透函數,二是反覆調用滲透函數實現排序的函數。
b.實例
初始序列:46,79,56,38,40,84
建堆:
交換,從堆中踢出最大數
依次類推:最後堆中剩餘的最後兩個結點交換,踢出一個,排序完成。
public void buildHeap(int[] arrays){
int arrayLength = arrays.length;
//循環建堆
for(int i=0; i<arrayLength-1; i++){
//建堆
buildMaxHeap(arrays, arrayLength-i-1);
//交換堆頂和最後一個元素
swap(arrays, 0, arrayLength-i-1);
System.out.println(Arrays.toString(arrays));
}
}
//對data數組從0到lastIndex建立大頂堆
public void buildMaxHeap(int[] data, int lastIndex){
//從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代表右節點存在
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;
}
}
}
}
//交換兩個元素
public void swap(int[] data, int i, int j){
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
3.交換排序
3.1冒泡排序
基本思想:在要排序的一組數中,對當前還未排好序的範圍內的全部數,自上而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要求相反時,就將它們互換;
public void bubbleSort(int[] arrays){
for(int i=0; i<arrays.length; i++){
//執行冒泡排序,大的元素下沉,小的元素上浮
for(int l=0; l<arrays.length - 1 - i; l++){
//-i表示最大的元素已經下沉到底部
if(arrays[l]>arrays[l+1]){
int temp = arrays[l+1];
arrays[l+1] = arrays[l];
arrays[l] = temp;
}
}
}
System.out.println(arrays);
}
3.2快速排序
基本思想:選擇一個基準元素,通常選擇第一個元素或者最後一個元素,通過一趟掃描,將待排序列分成兩部分,一部分比基準元素小,一部分大於等於基準元素,此時基準元素在其排好序後的正確位置,然後再用同樣的方法遞歸地排序劃分的兩部分;
public void quick(int[] arrays){
if(arrays.length>0){
quickSort(arrays, 0, arrays.length-1);
}
System.out.println(arrays);
}
public void quickSort(int[] arrays, int start, int end) {
if(start<end){
int mid = getMiddle(arrays, start, end); //計算中間位置
quickSort(arrays, start, mid-1); //左側元素遞歸
quickSort(arrays, mid+1, end); //右側元素遞歸
}
}
public int getMiddle(int[] arrays, int start, int end){
//中間位置元素
int temp = arrays[start];
while (start<end){
//右側開始判斷,中間位置元素小於等於右側,end--
while (start<end && temp<=arrays[end]){
end--;
}
arrays[start] = arrays[end];
//左側開始判斷,中間位置元素大於等於左側元素,start++
while (start<end && temp>=arrays[start]){
start++;
}
arrays[end] = arrays[start];
}
arrays[start] = temp;
//返回中間元素位置
return start;
}
4.歸併排序
基本思想:歸併(Merge)排序法是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分爲若干個子序列,每個子序列是有序的。然後再把有序子序列合併爲整體有序序列。
public int[] sort(int[] nums, int low, int high){
int mid = (low+high)/2;
if(low < high){
//左邊
sort(nums, low, mid);
//右邊
sort(nums, mid+1, high);
//左右歸併
merge(nums, low, mid, high);
}
return nums;
}
public void merge(int[] nums, int low, int mid, int high){
int[] temp = new int[high - low + 1];
int i = low; //左指針
int j = mid+1; //右指針
int k = 0;
//把較小的數先移到新數組
while (i<=mid && j<=high){
if(nums[i]<nums[j]){
temp[k++] = nums[i++];
}else{
temp[k++] = nums[j++];
}
}
//把左邊剩餘的數據移入數組
while (i<=mid){
temp[k++] = nums[i++];
}
//把右邊剩餘的數據移入數組
while (j<=high){
temp[k++] = nums[j++];
}
//把新數組中的數據覆蓋nums數組
for(int k2=0; k2<temp.length; k2++){
nums[k2+low] = temp[k2];
}
}
5.基數排序
基本思想:將所有待比較數值(正整數)統一爲同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後,數列就變成一個有序序列。
public void sort(int[] array){
//找到最大數,確定要排序幾趟
int max = 0;
for(int i=0; i<array.length; i++){
if(max<array[i]){
max = array[i];
}
}
//判斷位數
int times = 0;
while (max>0){
max = max/10;
times++;
}
//建立十個隊列
List<ArrayList> queue = new ArrayList<>();
for(int i=0; i < 10; i++){
ArrayList queueone = new ArrayList();
queue.add(queueone);
}
//進行times次分配和收集
for(int i=0; i<times; i++){
//分配
for(int j=0; j<array.length; j++){
int x = array[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
ArrayList queuetwo = queue.get(x);
queuetwo.add(array[j]);
queue.set(x, queuetwo);
}
//收集
int count = 0;
for(int j=0; j<10; j++){
while (queue.get(j).size()>0){
ArrayList<Integer> quequethree = queue.get(j);
array[count] = quequethree.get(0);
quequethree.remove(0);
count++;
}
}
}
}