十大排序算法全面解析-Java實現

前言

算法就是編程的靈魂,不會算法的程序員只配作碼農。以前看到這句話受到一萬點暴擊傷害!同時也激起了本身的鬥志,坦白說做爲一個程序員,我一直知道算法的重要性,可是在算法這一塊一直作的不夠好,甚至除了大學學過這門課程以後就不多去接觸它。由於一開始我就給算法貼上了難,煩,不怎麼用的標籤,如今想來其實都是在逃避問題。因此決定亡羊補牢,從頭開始!html

文章首發於我的博客【www.xiongfrblog.cnjava


介紹

算法的學習也是有着階段性的,從入門到簡單,再到複雜,再到簡單。最後的簡單是當你達到必定高度以後對於問題可以準確的找到最簡單的解答。就如同修仙同樣,真正的高手一招足以擊退萬馬千軍!程序員

算法裏邊最經常使用也是最基本的就是排序算法和查找算法了,本文主要講解算法裏邊最經典的十大排序算法。在這裏咱們根據他們各自的實現原理以及效率將十大排序算法分爲兩大類:算法

  1. 非線性比較類排序:非線性是指算法的時間複雜度不能突破(nlogn),元素之間經過比較大小來決定前後順序。
  2. 線性非比較類排序:算法的時間複雜度可以突破(nlogn),而且不經過比較來對元素排序。

具體分類咱們上圖說明: shell

排序算法分類

算法比較

這裏給出算法的時間複雜度,空間複雜度以及穩定性的對比整理,一樣經過圖片的形式給出:編程

算法比較

在這裏給出相關指標的解釋數組

時間複雜度:時間複雜度本意是預估算法的執行時間,但實際上一個程序在計算機上執行的速度是很是快的,時間幾乎能夠忽略不計了,也就是失去了意義,因此這裏意思是算法中執行頻度最高的代碼的執行的次數。反應當n發生變化時,執行次數的改變呈現一種什麼樣的規律。 空間複雜度:是指算法在計算機內執行時所需存儲空間的度量,它也是數據規模n的函數。 穩定性:在排序中對於相等的兩個元素a,b。若是排序前a在b的前邊,排序以後a也老是在b的前邊。他們的位置不會由於排序而改變稱之爲穩定。反之,若是排序後a,b的位置可能會發生改變,那麼就稱之爲不穩定。緩存

下面就一一對十大算法進行詳細的講解,會給出他們的基本思想,圖片演示,以及帶有詳細註釋的源碼。(本文全部的排序算法都是升序排序)app

1.冒泡排序

1.1 基本思想

冒泡排序能夠說是最簡單的排序之一了,也是大部分人最容易想到的排序。即對n個數進行排序,每次都是由前一個數跟後一個數比較,每循環一輪, 就能夠將最大的數移到數組的最後, 總共循環n-1輪,完成對數組排序。函數

1.2 圖片演示

冒泡排序

1.3 代碼展現

public static void bubbleSort(int[] arr) {
		if(arr==null)
			return;
		int len=arr.length;
		//i控制循環次數,長度爲len的數組只須要循環len-1次,i的起始值爲0因此i<len-1
		for(int i=0;i<len-1;i++) {
			//j控制比較次數,第i次循環內須要比較len-i次
			//可是因爲是由arr[j]跟arr[j+1]進行比較,因此爲了保證arr[j+1]不越界,j<len-i-1
			for(int j=0;j<len-i-1;j++) {
				//若是前一個數比後一個數大,則交換位置將大的數日後放。
				if(arr[j]>arr[j+1]) {
					int temp=arr[j+1];
					arr[j+1]=arr[j];
					arr[j]=temp;
				}
			}
		}
	}
複製代碼

2.選擇排序

2.1 基本思想

選擇排序能夠說是冒泡排序的改良版,再也不是前一個數跟後一個數相比較, 而是在每一次循環內都由一個數去跟 全部的數都比較一次,每次比較都選取相對較小的那個數來進行下一次的比較,並不斷更新較小數的下標 這樣在一次循環結束時就能獲得最小數的下標,再經過一次交換將最小的數放在最前面,經過n-1次循環以後完成排序。 這樣相對於冒泡排序來講,比較的次數並無改變,可是數據交換的次數大大減小。

2.2 圖片演示

選擇排序

2.3 代碼展現

public static void selectSort(int[] arr) {
		if(arr==null)
			return;
		int len=arr.length;
		//i控制循環次數,長度爲len的數組只須要循環len-1次,i的起始值爲0因此i<len-1
		for(int i=0;i<len-1;i++) {
			//minIndex 用來保存每次比較後較小數的下標。
			int minIndex=i;
			//j控制比較次數,由於每次循環結束以後最小的數都已經放在了最前面,
			//因此下一次循環的時候就能夠跳過這個數,因此j的初始值爲i+1而不須要每次循環都從0開始,而且j<len便可
			for(int j=i+1;j<len;j++) {
				//每比較一次都須要將較小數的下標記錄下來
				if(arr[minIndex]>arr[j]) {
					minIndex=j;
				}
			}
			//當完成一次循環時,就須要將本次循環選取的最小數移動到本次循環開始的位置。
			if(minIndex!=i) {
				int temp=arr[i];
				arr[i]=arr[minIndex];
				arr[minIndex]=temp;
			}
			//打印每次循環結束以後數組的排序狀態(方便理解)
			System.out.println("第"+(i+1)+"次循環以後效果:"+Arrays.toString(arr));
		}
	}
複製代碼

3.插入排序

3.1 基本思想

插入排序的思想打牌的人確定很容易理解,就是見縫插針。 首先就默認數組中的第一個數的位置是正確的,即已經排序。 而後取下一個數,與已經排序的數按從後向前的順序依次比較, 若是該數比當前位置排好序的數小,則將排好序的數的位置向後移一位。 重複上一步驟,直到找到合適的位置。 找到位置後就結束比較的循環,將該數放到相應的位置。

3.2 圖片演示

插入排序

3.3 代碼展現

public static void insertSort(int[] arr) {
		if(arr==null)
			return;
		int len=arr.length;
		//i控制循環次數,由於已經默認第一個數的位置是正確的,因此i的起始值爲1,i<len,循環len-1次
		for(int i=1;i<len;i++) {
			int j=i;//變量j用來記錄即將要排序的數的位置即目標數的原位置
			int target=arr[j];//target用來記錄即將要排序的那個數的值即目標值
			//while循環用來爲目標值在已經排好序的數中找到合適的位置,
			//由於是從後向前比較,而且是與j-1位置的數比較,因此j>0
			while(j>0 && target<arr[j-1]) {
				//當目標數的值比它當前位置的前一個數的值小時,將前一個數的位置向後移一位。
				//而且j--使得目標數繼續與下一個元素比較
				arr[j]=arr[j-1];
				j--;
			}
			//更目標數的位置。
			arr[j]=target;
			//打印每次循環結束以後數組的排序狀態(方便理解)
			System.out.println("第"+(i)+"次循環以後效果:"+Arrays.toString(arr));
		}
	}
複製代碼

4.希爾排序

4.1 基本思想

希爾排序也稱爲"縮小增量排序",原理是先將須要排的數組分紅多個子序列,這樣每一個子序列的元素個數就不多,再分別對每一個對子序列進行插入排序。在該數組基本有序後 再進行一次直接插入排序就能完成對整個數組的排序。因此,要採用跳躍分割的策略。這裏引入「增量」的概念,將相距某個增量的記錄兩兩組合成一個子序列,而後對每一個子序列進行直接插入排序, 這樣獲得的結果纔會使基本有序(即小的在前邊,大的在後邊,不大不小的在中間)。希爾排序就是 直接插入排序的升級版。

4.2 圖片演示

希爾排序

4.3 代碼展現

public static void shellSort(int[] arr) {
		if(arr==null)
			return;
		int len=arr.length;//數組的長度
		int k=len/2;//初始的增量爲數組長度的一半
		//while循環控制按增量的值來劃不一樣分子序列,每完成一次增量就減小爲原來的一半
		//增量的最小值爲1,即最後一次對整個數組作直接插入排序
		while(k>0) {
			//裏邊其實就是升級版的直接插入排序了,是對每個子序列進行直接插入排序,
			//因此直接將直接插入排序中的‘1’變爲‘k’就能夠了。
			for(int i=k;i<len;i++) {
				int j=i;
				int target=arr[i];
				while(j>=k && target<arr[j-k]) {
					arr[j]=arr[j-k];
					j-=k;
				}
				arr[j]=target;
			}
			//不一樣增量排序後的結果
			System.out.println("增量爲"+k+"排序以後:"+Arrays.toString(arr));
			k/=2;
		}
	}
複製代碼

5.歸併排序

5.1 基本思想

整體歸納就是從上到下遞歸拆分,而後從下到上逐步合併。

遞歸拆分:先把待排序數組分爲左右兩個子序列,再分別將左右兩個子序列拆分爲四個子子序列,以此類推直到最小的子序列元素的個數爲兩個或者一個爲止。

逐步合併(必定要注意是從下到上層級合併,能夠理解爲遞歸的層級返回):將最底層的最左邊的一個子序列排序,而後將從左到右第二個子序列進行排序,再將這兩個排好序的子序列合併並排序,而後將最底層從左到右第三個子序列進行排序..... 合併完成以後記憶完成了對數組的排序操做

5.2 圖片演示

歸併排序

5.3 代碼展現

public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr= {3,8,6,2,1,8};
		mergeSort(arr,0,arr.length-1);
		System.out.println(Arrays.toString(arr));
	}
	/** * 遞歸拆分 * @param arr 待拆分數組 * @param left 待拆分數組最小下標 * @param right 待拆分數組最大下標 */
	public static void mergeSort(int[] arr,int left,int right) {
		int mid=(left+right)/2; //中間下標
		if(left<right) {
			mergeSort(arr,left,mid);//遞歸拆分左邊
			mergeSort(arr,mid+1,right);//遞歸拆分右邊
			sort(arr,left,mid,right);//合併左右
		}
	}
	
	/** * 合併兩個有序子序列 * @param arr 待合併數組 * @param left 待合併數組最小下標 * @param mid 待合併數組中間下標 * @param right 待合併數組最大下標 */
	public static void sort(int[] arr,int left,int mid,int right) {
		int[] temp=new int[right-left+1]; //臨時數組,用來保存每次合併年以後的結果
		int i=left;
		int j=mid+1;
		int k=0;//臨時數組的初始下標
		//這個while循環可以初步篩選出待合併的了兩個子序列中的較小數
		while(i<=mid && j<=right) {
			if(arr[i]<=arr[j]) {
				temp[k++]=arr[i++];
			}else {
				temp[k++]=arr[j++];
			}
		}
		//將左邊序列中剩餘的數放入臨時數組
		while(i<=mid) {
			temp[k++]=arr[i++];
		}
		//將右邊序列中剩餘的數放入臨時數組
		while(j<=right) {
			temp[k++]=arr[j++];
		}
		//將臨時數組中的元素位置對應到真真實的數組中
		for(int m=0;m<temp.length;m++) {
			arr[m+left]=temp[m];
		}
	}
複製代碼

6.快速排序

6.1 基本思想

快速排序也採用了分治的策略,這裏引入了‘基準數’的概念。

  1. 找一個基準數(通常將待排序的數組的第一個數做爲基準數)
  2. 對數組進行分區,將小於等於基準數的所有放在左邊,大於基準數的所有放在右邊。
  3. 重複1,2步驟,分別對左右兩個子分區進行分區,一直到各分區只有一個數爲止。

6.2 圖片演示

快速排序

6.3 代碼展現

public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr= {72,6,57,88,60,42,83,73,48,85};
		quickSort(arr,0,9);
		System.out.println(Arrays.toString(arr));
	}
	/** * 分區過程 * @param arr 待分區數組 * @param left 待分區數組最小下標 * @param right 待分區數組最大下標 */
	public static void quickSort(int[] arr,int left,int right) {
		if(left<right) {
			int temp=qSort(arr,left,right);
			quickSort(arr,left,temp-1);
			quickSort(arr,temp+1,right);
		}
	}
	/** * 排序過程 * @param arr 待排序數組 * @param left 待排序數組最小下標 * @param right 待排序數組最大下標 * @return 排好序以後基準數的位置下標,方便下次的分區 */
	public static int qSort(int[] arr,int left,int right) {
		int temp=arr[left];//定義基準數,默認爲數組的第一個元素
		while(left<right) {//循環執行的條件
			//由於默認的基準數是在最左邊,因此首先從右邊開始比較進入while循環的判斷條件
			//若是當前arr[right]比基準數大,則直接將右指針左移一位,固然還要保證left<right
			while(left<right && arr[right]>temp) {
				right--;
			}
			//跳出循環說明當前的arr[right]比基準數要小,那麼直接將當前數移動到基準數所在的位置,而且左指針向右移一位(left++)
			//這時當前數(arr[right])所在的位置空出,須要從左邊找一個比基準數大的數來填充。
			if(left<right) {
				arr[left++]=arr[right];
			}
			//下面的步驟是爲了在左邊找到比基準數大的數填充到right的位置。
			//由於如今須要填充的位置在右邊,因此左邊的指針移動,若是arr[left]小於或者等於基準數,則直接將左指針右移一位
			while(left<right && arr[left]<=temp) {
				left++;
			}
			//跳出上一個循環說明當前的arr[left]的值大於基準數,須要將該值填充到右邊空出的位置,而後當前位置空出。
			if(left<right) {
				arr[right--]=arr[left];
			}
		}
		//當循環結束說明左指針和右指針已經相遇。而且相遇的位置是一個空出的位置,
		//這時候將基準數填入該位置,並返回該位置的下標,爲分區作準備。
		arr[left]=temp;
		return left;
	}
複製代碼

7.堆排序

7.1 基本思想

在此以前要先說一下堆的概念,是一種特殊的徹底二叉樹,分爲大頂堆和小頂堆。

大頂堆:每一個結點的值都大於它的左右子結點的值,升序排序用大頂堆。

小頂堆:每一個結點的值都小於它的左右子結點的值,降序排序用小頂堆。

因此,須要先將待排序數組構形成大頂堆的格式,這時候該堆的頂結點就是最大的數,將其與堆的最後一個結點的元素交換。再將剩餘的樹從新調整成堆,再次首節點與尾結點交換,重複執行直到只剩下最後一個結點完成排序。

7.2 圖片演示

堆排序

7.3 代碼展現

public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr= {72,6,57,88,60,42,83,73,48,85};
		heapSort(arr);
		System.out.println(Arrays.toString(arr));
	}

	public static void heapSort(int[] arr) {
		if(arr==null) {
			return;
		}
		int len=arr.length;
		//初始化大頂堆(從最後一個非葉節點開始,從左到右,由下到上)
		for(int i=len/2-1;i>=0;i--) {
			adjustHeap(arr,i,len);
		}
		//將頂節點和最後一個節點互換位置,再將剩下的堆進行調整
		for(int j=len-1;j>0;j--) {
			swap(arr,0,j);
			adjustHeap(arr,0,j);
		}
	}
	
	/** * 整理樹讓其變成堆 * @param arr 待整理的數組 * @param i 開始的結點 * @param j 數組的長度 */
	public static void adjustHeap(int[] arr,int i,int j) {
		int temp=arr[i];//定義一個變量保存開始的結點
		//k就是該結點的左子結點下標
		for(int k=2*i+1;k<j;k=2*k+1) {
			//比較左右兩個子結點的大小,k始終記錄二者中較大值的下標
			if(k+1<j && arr[k]<arr[k+1]) {
				k++;
			}
			//經子結點中的較大值和當前的結點比較,比較結果的較大值放在當前結點位置
			if(arr[k]>temp) {
				arr[i]=arr[k];
				i=k;
			}else{//說明已是大頂堆
				break;
			}
		}
		arr[i]=temp;
	}
	
	/** * 交換數據 * @param arr * @param num1 * @param num2 */
	public static void swap(int[] arr, int num1,int num2) {
		int temp=arr[num1];
		arr[num1]=arr[num2];
		arr[num2]=temp;
	}
複製代碼

8.計數排序

8.1 基本思想

計數排序採用了一種全新的思路,再也不是經過比較來排序,而是將待排序數組中的最大值+1做爲一個臨時數組的長度,而後用臨時數組記錄待排序數組中每一個元素出現的次數。最後再遍歷臨時數組,由於是升序,因此從前到後遍歷,將臨時數組中值>0的數的下標循環取出,依次放入待排序數組中,便可完成排序。計數排序的效率很高,可是實在犧牲內存的前提下,而且有着限制,那就是待排序數組的值必須 限制在一個肯定的範圍。

8.2 圖片演示

計數排序

8.3 代碼展現

public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr= {72,6,57,88,60,42,83,73,48,85};
		countSort(arr);
		System.out.println(Arrays.toString(arr));
	}

	public static void countSort(int[] arr) {
		if(arr==null)
			return;
		int len=arr.length;
		//保存待排序數組中的最大值,目的是肯定臨時數組的長度(必須)
		int maxNum=arr[0];
		//保存待排序數組中的最小值,目的是肯定最終遍歷臨時數組時下標的初始值(非必需,只是這樣能夠加快速度,減小循環次數)
		int minNum=arr[0];
		//for循環就是爲了找到待排序數組的最大值和最小值
		for(int i=1;i<len;i++) {
			maxNum=maxNum>arr[i]?maxNum:arr[i];
			minNum=minNum<arr[i]?minNum:arr[i];
		}
		//建立一個臨時數組
		int[] temp=new int[maxNum+1];
		//for循環是爲了記錄待排序數組中每一個元素出現的次數,並將該次數保存到臨時數組中
		for(int i=0;i<len;i++) {
			temp[arr[i]]++;
		}
		//k=0用來記錄待排序數組的下標
		int k=0;
		//遍歷臨時數組,從新爲待排序數組賦值。
		for(int i=minNum;i<temp.length;i++) {
			while(temp[i]>0) {
				arr[k++]=i;
				temp[i]--;
			}
		}
	}
複製代碼

9.桶排序

9.1 基本思想

桶排序其實就是計數排序的強化版,須要利用一個映射函數首先定義有限個數個桶,而後將待排序數組內的元素按照函數映射的關係分別放入不一樣的桶裏邊,如今不一樣的桶裏邊的數據已經作了區分,好比A桶裏的數要麼所有大於B桶,要麼所有小於B桶裏的數。可是A,B桶各自裏邊的數仍是亂序的。因此要藉助其餘排序方式(快速,插入,歸併)分別對每個元素個數大於一的桶裏邊的數據進行排序。最後再將桶裏邊的元素按照順序依次放入待排序數組中便可。

9.2 圖片演示

桶排序

9.3 代碼展現

public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr= {72,6,57,88,60,42,83,73,48,85};
		bucketSort(arr);
		System.out.println(Arrays.toString(arr));
	}
	
	public static void bucketSort(int[] arr) {
		if(arr==null)
			return;
		int len=arr.length;
		//定義桶的個數,這裏k的值要視狀況而定,這裏咱們假設待排序數組裏的數都是[0,100)之間的。
		int k=10;
		//用嵌套集合來模擬桶,外層集合表示桶,內層集合表示桶裏邊裝的元素。
		List<List<Integer>> bucket=new ArrayList<>();
		//for循環初始化外層集合即初始化桶
		for(int i=0;i<k;i++){
			bucket.add(new ArrayList<>());
		}
		//循環是爲了將待排序數組中的元素經過映射函數分別放入不一樣的桶裏邊
		for(int i=0;i<len;i++) {
			bucket.get(mapping(arr[i])).add(arr[i]);
		}
		//這個循環是爲了將全部的元素個數大於1的桶裏邊的數據進行排序。
		for(int i=0;i<k;i++) {
			if(bucket.size()>1) {
				//由於這裏是用集合來模擬的桶因此用java寫好的對集合排序的方法。
				//其實應該本身寫一個方法來排序的。
				Collections.sort(bucket.get(i));
			}
			 
		}
		//將排好序的數從新放入待排序數組中
		int m=0;
		for(List<Integer> list:bucket) {
			if(list.size()>0) {
				for(Integer a:list) {
					arr[m++]=a;
				}
			}
		}
	}
	/** * 映射函數 * @param num * @return */
	public static int mapping(int num) {
		return num/10;
	}
複製代碼

10.基數排序

10.1基本思想

就是將待排序數據拆分紅多個關鍵字進行排序,也就是說,基數排序的實質是多關鍵字排序。多關鍵字排序的思路是將待排數據裏德排序關鍵字拆分紅多個排序關鍵字; 第1個排序關鍵字,第2個排序關鍵字,第3個排序關鍵字......而後,根據子關鍵字對待排序數據進行排序。

10.2 圖片演示

基數排序

10.3 代碼展現

public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr= {720,6,57,88,60,42,83,73,48,85};
		redixSort(arr,10,3);
		System.out.println(Arrays.toString(arr));
	}

	public static void redixSort(int[] arr, int radix, int d) {  
        // 緩存數組 
        int[] tmp = new int[arr.length];  
        // buckets用於記錄待排序元素的信息 
        // buckets數組定義了max-min個桶 
        int[] buckets = new int[radix];  
  
        for (int i = 0, rate = 1; i < d; i++) {  
  
            // 重置count數組,開始統計下一個關鍵字 
            Arrays.fill(buckets, 0);  
            // 將data中的元素徹底複製到tmp數組中 
            System.arraycopy(arr, 0, tmp, 0, arr.length);  
  
            // 計算每一個待排序數據的子關鍵字 
            for (int j = 0; j < arr.length; j++) {  
                int subKey = (tmp[j] / rate) % radix;  
                buckets[subKey]++;  
            }  
  
            for (int j = 1; j < radix; j++) {  
                buckets[j] = buckets[j] + buckets[j - 1];  
            }  
  
            // 按子關鍵字對指定的數據進行排序 
            for (int m = arr.length - 1; m >= 0; m--) {  
                int subKey = (tmp[m] / rate) % radix;  
                arr[--buckets[subKey]] = tmp[m];  
            }  
            rate *= radix;  
        }  
    }
複製代碼

參考資料

  1. www.cnblogs.com/onepixel/ar…
  2. blog.csdn.net/apei830/art…
相關文章
相關標籤/搜索