各種排序演算法比較(java)
排序演算法是資料結構中十分基礎的內容,本文總結了常用的排序演算法的原理和效能,還給出了相關的圖解,並且採用java語言實現了演算法,最後給了一個面試中實際的例子,以及演算法複雜度的比較
1、選擇排序
最基本的排序演算法,原理看圖就可以理解:
// 選擇排序
public int[] selectsort(int[] arr)
{
for(int x=0;x<arr.length-1;x++) //最後一個數不用在自己和自己進行比較了,n-1輪
{
for(int y=x+1;y<arr.length;y++)
{
if (arr[x]>arr[y])
{
int temp=arr[x];
arr[x]=arr[y];
arr[y]=temp;
}
}
}
return arr;
}
2、插入排序
將當前元素和左邊的元素比較,若當前元素小,就交換兩者,也就相當於插入
// 插入排序
public int[] insertionSort (int[] arr) {
int len = arr.length;
for (int i = 1; i < len; i++)
{
// j表示當前元素的位置,將其和左邊的元素比較,若當前元素小,就交換,也就相當於插入
// 這樣當前元素位於j-1處,j--來更新當前元素,j一直左移不能越界,因此應該大於0
for(int j=i; j>0 && arr[j]<arr[j-1];j--)
{
int temp = arr[j]; // 元素交換
arr[j] = arr[j-1];
arr[j-1] = temp;
}
}
return arr;
}
3、氣泡排序
相鄰的兩個元素進行比較,如果符合條件就換位,這樣第一輪,最大的數會在最後面,長度在依次遞減
// 氣泡排序
public int[] bubbleSort(int[] arr) {
int len = arr.length;
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j+1]) { // 相鄰元素兩兩對比
int temp = arr[j+1]; // 元素交換
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
4、快速排序
/**
* 將陣列的某一段元素進行劃分,小的在左邊,大的在右邊
*/
public static int divide(int[] a, int start, int end){
//每次都以最右邊的元素作為基準值
int base = a[end];
//start一旦等於end,就說明左右兩個指標合併到了同一位置,可以結束此輪迴圈。
while(start < end){
while(start < end && a[start] <= base)
//從左邊開始遍歷,如果比基準值小,就繼續向右走
start++;
//上面的while迴圈結束時,就說明當前的a[start]的值比基準值大,應與基準值進行交換
if(start < end){
//交換
int temp = a[start];
a[start] = a[end];
a[end] = temp;
//交換後,此時的那個被調換的值也同時調到了正確的位置(基準值右邊),因此右邊也要同時向前移動一位
end--;
}
while(start < end && a[end] >= base)
//從右邊開始遍歷,如果比基準值大,就繼續向左走
end--;
//上面的while迴圈結束時,就說明當前的a[end]的值比基準值小,應與基準值進行交換
if(start < end){
//交換
int temp = a[start];
a[start] = a[end];
a[end] = temp;
//交換後,此時的那個被調換的值也同時調到了正確的位置(基準值左邊),因此左邊也要同時向後移動一位
start++;
}
}
//這裡返回start或者end皆可,此時的start和end都為基準值所在的位置
return end;
}
/**
* 排序
*/
public static void sort(int[] a, int start, int end){
if(start > end){
//如果只有一個元素,就不用再排下去了
return;
}
else{
//如果不止一個元素,繼續劃分兩邊遞迴排序下去
int partition = divide(a, start, end);
sort(a, start, partition-1);
sort(a, partition+1, end);
}
}
5、歸併排序
治的最後過程舉例
package cn;
import java.util.Arrays;
// 歸併排序
public class MergeSort {
public static void main(String []args)
{
int []arr = {9,8,7,6,5,4,3,2,1};
sort(arr,0,8);
System.out.println(Arrays.toString(arr));
}
private static void sort(int[] arr,int start,int end)
{
if(start<end)
{
int mid = (start+end)/2;
sort(arr,start,mid);//左邊歸併排序,使得左子序列有序
sort(arr,mid+1,end);//右邊歸併排序,使得右子序列有序
merge(arr,start,mid,mid+1,end);//將兩個有序子數組合並操作
}
}
private static void merge(int[] arr,int start1,int end1,int start2,int end2)
{
// 建立輔助陣列
int len=end2-start1+1;
int[] temp=new int[len];
int i = start1;//左序列指標
int j = start2;//右序列指標
int t = 0;//臨時陣列指標
while (i<=end1 && j<=end2)
{ // 將小的放入輔助陣列
if(arr[i]<=arr[j])
{
temp[t++] = arr[i++];
}else
{
temp[t++] = arr[j++];
}
}
//若左序列此時還有有剩餘的,將左邊剩餘元素填充進temp中
while(i<=end1)
{
temp[t++] = arr[i++];
}
//若右序列此時還有有剩餘的,將右序列剩餘元素填充進temp中
while(j<=end2)
{
temp[t++] = arr[j++];
}
t = 0;
//將temp中的元素全部拷貝到原陣列中
while(start1 <= end2){
arr[start1++] = temp[t++];
}
}
}
結果
[1, 2, 3, 4, 5, 6, 7, 8, 9]
6、堆排序
堆排序是利用堆這種資料結構而設計的一種排序演算法,堆排序是一種選擇排序,它的最壞,最好,平均時間複雜度均為O(nlogn),它也是不穩定排序。首先簡單瞭解下堆結構。堆是具有以下性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱為大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱為小頂堆。如下圖:父節點比較大的是大頂堆,父節點比較小的是小頂堆
堆排序基本思想及步驟
堆排序的基本思想是:將待排序序列構造成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點。將最大值放到末尾(最大值和末尾元素交換),此時末尾就為最大值。然後將剩餘n-1個元素重新構造成一個堆,這樣會得到n個元素的次小值。如此反覆執行,便能得到一個有序序列了.
再簡單總結下堆排序的基本思路:
a.將無需序列構建成一個堆,根據升序降序需求選擇大頂堆或小頂堆;
b.將堆頂元素與末尾元素交換,將最大元素”沉”到陣列末端;
c.重新調整結構,使其滿足堆定義,然後繼續交換堆頂元素與當前末尾元素,反覆執行調整+交換步驟,直到整個序列有序。
package cn;
import java.util.Arrays;
/*
* 堆排序demo
*/
public class HeapSort
{
public static void main(String []args)
{
int []arr = {1,7,2,9,3,8,6,5,4};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int []arr)
{
//1.構建大頂堆
for(int i=arr.length/2-1;i>=0;i--)
{
//從最後一個一個非葉子結點i從下至上,從右至左調整結構
adjustHeap(arr,i,arr.length);
}
//2.調整堆結構+交換堆頂元素與末尾元素
for(int j=arr.length-1;j>0;j--){
swap(arr,0,j);//將堆頂元素與末尾元素進行交換
adjustHeap(arr,0,j);//重新對堆進行調整
}
}
/*
* 調整大頂堆
*/
public static void adjustHeap(int []arr,int i,int length)
{
//i是父節點
while(2*i+1<length)
{
// k是子節點
int k=i*2+1;
//從i結點的左子結點開始,也就是2i+1處開始,使得k指向i的左右子節點中較大的一個
// 如果不存在右子節點,就不用比較了
if(k+1<length && arr[k]<arr[k+1])
{
//如果左子結點小於右子結點,k指向右子結點
k++;
}
// 比較該較大節點與父節點
if(arr[k] <arr[i]) break;
//如果子節點大於父節點,將子節點和父節點交換
swap(arr,i,k);
// 更新父節點,調整下面的子樹
i = k;
}
}
/*
* 交換元素
*/
public static void swap(int []arr,int a ,int b)
{
int temp=arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
結果:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
7、希爾排序
希爾排序為了加快速度簡單地改進了插入排序。 使陣列中任意間隔為gap的元素都是有序的。下圖中gap=4:
package cn;
import java.util.Arrays;
/*
*希爾排序
*/
public class ShellSort {
public static void main(String []args){
int []arr ={1,4,2,7,9,8,3,6};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int [] arr)
{
//確定初始的增量gap,保證其不能越界
int gap=1;
while(gap<arr.length/3)
gap=3*gap+1;
// 對於相隔gap的元素進行插入排序
while(gap>=1)
{
for(int i=gap;i<arr.length;i++)
{
for(int j=i;j>=gap && arr[j]<arr[j-gap]; j-=gap)
swap(arr,j,j-gap);
}
gap=gap/3;
}
}
/*
* 交換陣列元素
*/
public static void swap(int []arr,int a,int b){
arr[a] = arr[a]+arr[b];
arr[b] = arr[a]-arr[b];
arr[a] = arr[a]-arr[b];
}
}
結果:
[1, 2, 3, 4, 6, 7, 8, 9]
8、排序演算法的實際場景:
對於公司中所有員工的年齡進行排序,員工大概有幾萬人,要求時間效率是o(n),可以使用輔助空間但是不能超過O(n)
package cn;
import java.util.Arrays;
public class SortAges {
public static void SortAges(int ages[],int length)
{
if(ages==null || length<=0)
return;
// 存放各個年齡出現的次數,年齡範圍是0-99,初始值全都是0
int[] timesOfAge=new int[100];
for(int i=0;i<=99;i++)
{
timesOfAge[i]=0;
}
// 遍歷陣列,統計次數
for(int i=0;i<length;i++)
{
int age=ages[i];
if(age<0||age>99)
throw new RuntimeException();
timesOfAge[age]++;
}
// 年齡排序一定是0 1 2、、99這種,關鍵在於需要設定幾次,遍歷所有年齡,該年齡出現幾次,就在ages陣列中寫幾次該年齡
int index=0;
for(int i=0;i<=99;i++)
{
for(int j=0;j<timesOfAge[i];j++)
{
ages[index]=i;
index++;
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr={1,3,3,44,5,6,55,55,55};
SortAges(arr,9);
System.out.println(Arrays.toString(arr));
}
}
結果:
[1, 3, 3, 5, 6, 44, 55, 55, 55]