天天看點

資料結構常見八大排序算法java實作詳細整理排序的分類:

文章目錄

  • 排序的分類:
    • 冒泡排序
    • 選擇排序
    • 插入排序
    • 希爾排序
    • 快速排序
    • 歸并排序
    • 基數排序
    • 堆排序
    • 常用排序算法總結和對比

排序的分類:

  1. 内部排序:

指将需要處理的所有資料都加載到内部存儲器中進行排序。

  1. 外部排序法:

資料量過大,無法全部加載到記憶體中,需要借助外部存儲進行排序。

在這裡插入圖檔描述

資料結構常見八大排序算法java實作詳細整理排序的分類:

冒泡排序

冒泡排序(Bubble Sorting)的基本思想是:通過對待排序序列從前向後(從下标較小的元素開始),依次比較相鄰元素的值,若發現逆序則交換,使值較大的元素逐漸從前移向後部,就象水底下的氣泡一樣逐漸向上冒。
因為排序的過程中,各元素不斷接近自己的位置,如果一趟比較下來沒有進行過交換,就說明序列有序,是以要在排序過程中設定一個标志flag判斷元素是否進行過交換。進而減少不必要的比較。(這裡說的優化,可以在冒泡排序寫好後,在進行)
//	// 将前面額冒泡排序算法,封裝成一個方法
	public static void bubbleSort(int[] arr) {
		// 冒泡排序 的時間複雜度 O(n^2), 自己寫出
		int temp = 0; // 臨時變量
		boolean flag = false; // 辨別變量,表示是否進行過交換
		for (int i = 0; i < arr.length - 1; i++) {

			for (int j = 0; j < arr.length - 1 - i; j++) {
				// 如果前面的數比後面的數大,則交換
				if (arr[j] > arr[j + 1]) {
					flag = true;
					temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}
			//System.out.println("第" + (i + 1) + "趟排序後的數組");
			//System.out.println(Arrays.toString(arr));

			if (!flag) { // 在一趟排序中,一次交換都沒有發生過
				break;
			} else {
				flag = false; // 重置flag!!!, 進行下次判斷
			}
		}
	}
           

選擇排序

選擇式排序也屬于内部排序法,是從欲排序的資料中,按指定的規則選出某一進制素,再依規定交換位置後達到排序的目的。

選擇排序(select sorting)也是一種簡單的排序方法。它的基本思想是:第一次從arr[0]–arr[n-1]中選取最小值,與arr[0]交換,第二次從arr[1]–arr[n-1]中選取最小值,與arr[1]交換,第三次從arr[2]–arr[n-1]中選取最小值,與arr[2]交換,…,第i次從arr[i-1]–arr[n-1]中選取最小值,與arr[i-1]交換,…,

第n-1次從arr[n-2]~arr[n-1]中選取最小值,與arr[n-2]交換,總共通過n-1次,得到一個按排序碼從小到大排列的有序序列。

//選擇排序
	public static void selectSort(int[] arr) {



		//在推導的過程,我們發現了規律,是以,可以使用for來解決
		//選擇排序時間複雜度是 O(n^2)
		for (int i = 0; i < arr.length - 1; i++) {
			int minIndex = i;
			int min = arr[i];
			for (int j = i + 1; j < arr.length; j++) {
				if (min > arr[j]) { // 說明假定的最小值,并不是最小
					min = arr[j]; // 重置min
					minIndex = j; // 重置minIndex
				}
			}

			// 将最小值,放在arr[0], 即交換
			if (minIndex != i) {  //如果minIndex = i 即i=j時 此時剛好不用交換
				arr[minIndex] = arr[i];
				arr[i] = min;
			}

			//System.out.println("第"+(i+1)+"輪後~~");
			//System.out.println(Arrays.toString(arr));// 1, 34, 119, 101
		}
	}	
           

插入排序

插入式排序屬于内部排序法,是對于欲排序的元素以插入的方式找尋該元素的适當位置,以達到排序的目的。

插入排序(Insertion

Sorting)的基本思想是:把n個待排序的元素看成為一個有序表和一個無序表,開始時有序表中隻包含一個元素,無序表中包含有n-1個元素,排序過程中每次從無序表中取出第一個元素,把它的排序碼依次與有序表元素的排序碼進行比較,将它插入到有序表中的适當位置,使之成為新的有序表。

//插入排序
	public static void insertSort(int[] arr) {
		int insertVal = 0;
		int insertIndex = 0;
		//使用for循環來把代碼簡化
		for(int i = 1; i < arr.length; i++) {
			//定義待插入的數
			insertVal = arr[i];
			insertIndex = i - 1; // 即arr[1]的前面這個數的下标

			// 給insertVal 找到插入的位置
			// 說明
			// 1. insertIndex >= 0 保證在給insertVal 找插入位置,不越界
			// 2. insertVal < arr[insertIndex] 待插入的數,還沒有找到插入位置
			// 3. 就需要将 arr[insertIndex] 後移
			while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
				arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
				insertIndex--;
			}
			// 當退出while循環時,說明插入的位置找到, insertIndex + 1
			// 舉例:了解不了,我們一會 debug
			//這裡我們判斷是否需要指派
			if(insertIndex + 1 != i) {
				arr[insertIndex + 1] = insertVal;
			}

			//System.out.println("第"+i+"輪插入");
			//System.out.println(Arrays.toString(arr));
		}
    }
           

希爾排序

簡單插入排序存在的問題

我們看簡單的插入排序可能存在的問題.

數組 arr = {2,3,4,5,6,1} 這時需要插入的數 1(最小), 這樣的過程是:

{2,3,4,5,6,6}

{2,3,4,5,5,6}

{2,3,4,4,5,6}

{2,3,3,4,5,6}

{2,2,3,4,5,6}

{1,2,3,4,5,6}

結論: 當需要插入的數是較小的數時,後移的次數明顯增多,對效率有影響.

希爾排序法介紹

希爾排序是希爾(Donald Shell)于1959年提出的一種排序算法。希爾排序也是一種插入排序,它是簡單插入排序經過改進之後的一個更高效的版本,也稱為縮小增量排序。

希爾排序法基本思想

希爾排序是把記錄按下标的一定增量分組,對每組使用直接插入排序算法排序;随着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個檔案恰被分成一組,算法便終止

希爾排序時, 對有序序列在插入時采用移動法(效率比交換法高)

//對交換式的希爾排序進行優化->移位法
	public static void shellSort2(int[] arr) {
		
		// 增量gap, 并逐漸的縮小增量
		for (int gap = arr.length / 2; gap > 0; gap /= 2) {
			// 從第gap個元素,逐個對其所在的組進行直接插入排序
			for (int i = gap; i < arr.length; i++) {
				int j = i;
				int temp = arr[j];
				if (arr[j] < arr[j - gap]) {   //這裡if語句可以不加  但是可以減少進入while循環的次數
					while (j - gap >= 0 && temp < arr[j - gap]) {
						//移動
						arr[j] = arr[j-gap];
						j -= gap;   //循環與前面的相比
					}
					//當退出while後,就給temp找到插入的位置
					arr[j] = temp;
				}

			}
		}
	}
		// 使用逐漸推導的方式來編寫希爾排序
	// 希爾排序時, 對有序序列在插入時采用交換法, 
	// 思路(算法) ===> 代碼
	public static void shellSort(int[] arr) {
		
		int temp = 0;
		int count = 0;
		// 根據前面的逐漸分析,使用循環處理
		for (int gap = arr.length / 2; gap > 0; gap /= 2) {
			for (int i = gap; i < arr.length; i++) {
				// 周遊各組中所有的元素(共gap組,每組有個元素), 步長gap
				for (int j = i - gap; j >= 0; j -= gap) {
					// 如果目前元素大于加上步長後的那個元素,說明交換
					if (arr[j] > arr[j + gap]) {
						temp = arr[j];
						arr[j] = arr[j + gap];
						arr[j + gap] = temp;
					}
				}
			}
			//System.out.println("希爾排序第" + (++count) + "輪 =" + Arrays.toString(arr));
		}
	}
           

快速排序

快速排序(Quicksort)是對冒泡排序的一種改進。

基本思想是:通過一趟排序将要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分别進行快速排序,整個排序過程可以遞歸進行,以此達到整個資料變成有序序列

以第一個數為基準

資料結構常見八大排序算法java實作詳細整理排序的分類:
//方法1:便于了解
public static void quickSort2(int[] arr,int low,int high){
		int i,j,temp,t;
		if(low>high){
			return;
		}
		i=low;//起始位
		j=high;
		//temp就是基準位
		temp = arr[low];
		while (i<j) {
			//先看右邊,依次往左遞減
			while (temp<=arr[j]&&i<j) {
				j--;
			}
			//再看左邊,依次往右遞增
			while (temp>=arr[i]&&i<j) {
				i++;
			}
			//如果滿足條件則交換
			if (i<j) {
				t = arr[j];
				arr[j] = arr[i];
				arr[i] = t;
			}

		}
		//最後将基準為與i和j相等位置的數字交換
		arr[low] = arr[i];
		arr[i] = temp;
		//遞歸調用左半數組
		quickSort(arr, low, j-1);
		//遞歸調用右半數組
		quickSort(arr, j+1, high);
	}



	public static void quickSort(int[] arr,int left, int right) {
		int l = left; //左下标
		int r = right; //右下标
		//pivot 中軸值
		int pivot = arr[(left + right) / 2];
		int temp = 0; //臨時變量,作為交換時使用
		//while循環的目的是讓比pivot 值小放到左邊
		//比pivot 值大放到右邊
		while( l < r) { 
			//在pivot的左邊一直找,找到大于等于pivot值,才退出
			while( arr[l] < pivot) {
				l += 1;
			}
			//在pivot的右邊一直找,找到小于等于pivot值,才退出
			while(arr[r] > pivot) {
				r -= 1;
			}
			//如果l >= r說明pivot 的左右兩的值,已經按照左邊全部是
			//小于等于pivot值,右邊全部是大于等于pivot值
			if( l >= r) {
				break;
			}
			
			//交換
			temp = arr[l];
			arr[l] = arr[r];
			arr[r] = temp;
			
			//如果交換完後,發現這個arr[l] == pivot值 相等 r--, 前移
			if(arr[l] == pivot) {
				r -= 1;
			}
			//如果交換完後,發現這個arr[r] == pivot值 相等 l++, 後移
			if(arr[r] == pivot) {
				l += 1;
			}
		}
		
		// 如果 l == r, 必須l++, r--, 否則為出現棧溢出
		if (l == r) {
			l += 1;
			r -= 1;
		}
		//向左遞歸
		if(left < r) {
			quickSort(arr, left, r);
		}
		//向右遞歸
		if(right > l) {
			quickSort(arr, l, right);
		}
		
		
	}
           

歸并排序

歸并排序(MERGE-SORT)是利用歸并的思想實作的排序方法,該算法采用經典的分治(divide-and-conquer)政策(分治法将問題分(divide)成一些小的問題然後遞歸求解,而治(conquer)的階段則将分的階段得到的各答案"修補"在一起,即分而治之)。
//分+合方法
	public static void mergeSort(int[] arr, int left, int right, int[] temp) {
		if(left < right) {
			int mid = (left + right) / 2; //中間索引
			//向左遞歸進行分解
			mergeSort(arr, left, mid, temp);
			//向右遞歸進行分解
			mergeSort(arr, mid + 1, right, temp);
			//合并
			merge(arr, left, mid, right, temp);
			
		}
	}
	
	//合并的方法
	/**
	 * 
	 * @param arr 排序的原始數組
	 * @param left 左邊有序序列的初始索引
	 * @param mid 中間索引
	 * @param right 右邊索引
	 * @param temp 做中轉的數組
	 */
	public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
		
		int i = left; // 初始化i, 左邊有序序列的初始索引
		int j = mid + 1; //初始化j, 右邊有序序列的初始索引
		int t = 0; // 指向temp數組的目前索引
		
		//(一)
		//先把左右兩邊(有序)的資料按照規則填充到temp數組
		//直到左右兩邊的有序序列,有一邊處理完畢為止
		while (i <= mid && j <= right) {//繼續
			//如果左邊的有序序列的目前元素,小于等于右邊有序序列的目前元素
			//即将左邊的目前元素,填充到 temp數組 
			//然後 t++, i++
			if(arr[i] <= arr[j]) {
				temp[t] = arr[i];
				t += 1;
				i += 1;
			} else { //反之,将右邊有序序列的目前元素,填充到temp數組
				temp[t] = arr[j];
				t += 1;
				j += 1;
			}
		}
		
		//(二)
		//把有剩餘資料的一邊的資料依次全部填充到temp
		while( i <= mid) { //左邊的有序序列還有剩餘的元素,就全部填充到temp
			temp[t] = arr[i];
			t += 1;
			i += 1;	
		}
		
		while( j <= right) { //右邊的有序序列還有剩餘的元素,就全部填充到temp
			temp[t] = arr[j];
			t += 1;
			j += 1;	
		}
		
		
		//(三)
		//将temp數組的元素拷貝到arr
		//注意,并不是每次都拷貝所有
		t = 0;
		int tempLeft = left; // 
		//第一次合并 tempLeft = 0 , right = 1 //  tempLeft = 2  right = 3 // tL=0 ri=3
		//最後一次 tempLeft = 0  right = 7
		while(tempLeft <= right) { 
			arr[tempLeft] = temp[t];
			t += 1;
			tempLeft += 1;
		}
		
	}
           

基數排序

基數排序(桶排序)介紹

1)基數排序(radix sort)屬于“配置設定式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort,顧名思義,它是通過鍵值的各個位的值,将要排序的元素配置設定至某些“桶”中,達到排序的作用

2)基數排序法是屬于穩定性的排序,基數排序法的是效率高的穩定性排序法

3)基數排序(Radix Sort)是桶排序的擴充

4)基數排序是1887年赫爾曼·何樂禮發明的。它是這樣實作的:将整數按位數切割成不同的數字,然後按每個位數分别比較。

基數排序基本思想

将所有待比較數值統一為同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後,

數列就變成一個有序序列。

//基數排序方法
	public static void radixSort(int[] arr) {
		
		//根據前面的推導過程,我們可以得到最終的基數排序代碼
		
		//1. 得到數組中最大的數的位數
		int max = arr[0]; //假設第一數就是最大數
		for(int i = 1; i < arr.length; i++) {
			if (arr[i] > max) {
				max = arr[i];
			}
		}
		//得到最大數是幾位數
		int maxLength = (max + "").length();
		
		
		//定義一個二維數組,表示10個桶, 每個桶就是一個一維數組
		//說明
		//1. 二維數組包含10個一維數組
		//2. 為了防止在放入數的時候,資料溢出,則每個一維數組(桶),大小定為arr.length
		//3. 名明确,基數排序是使用空間換時間的經典算法
		int[][] bucket = new int[10][arr.length];
		
		//為了記錄每個桶中,實際存放了多少個資料,我們定義一個一維數組來記錄各個桶的每次放入的資料個數
		//可以這裡了解
		//比如:bucketElementCounts[0] , 記錄的就是  bucket[0] 桶的放入資料個數
		int[] bucketElementCounts = new int[10];
		
		
		//這裡我們使用循環将代碼處理
		
		for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {
			//(針對每個元素的對應位進行排序處理), 第一次是個位,第二次是十位,第三次是百位..
			for(int j = 0; j < arr.length; j++) {
				//取出每個元素的對應位的值
				int digitOfElement = arr[j] / n % 10;
				//放入到對應的桶中
				bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
				bucketElementCounts[digitOfElement]++;
			}
			//按照這個桶的順序(一維數組的下标依次取出資料,放入原來數組)
			int index = 0;
			//周遊每一桶,并将桶中是資料,放入到原數組
			for(int k = 0; k < bucketElementCounts.length; k++) {
				//如果桶中,有資料,我們才放入到原數組
				if(bucketElementCounts[k] != 0) {
					//循環該桶即第k個桶(即第k個一維數組), 放入
					for(int l = 0; l < bucketElementCounts[k]; l++) {
						//取出元素放入到arr
						arr[index++] = bucket[k][l];
					}
				}
				//第i+1輪處理後,需要将每個 bucketElementCounts[k] = 0 !!!!
				bucketElementCounts[k] = 0;
				
			}
			//System.out.println("第"+(i+1)+"輪,對個位的排序處理 arr =" + Arrays.toString(arr));
			
		}
	}
           

基數排序是經典的空間換時間的排序方法,當資料量過大時,容易造成記憶體溢出

資料結構常見八大排序算法java實作詳細整理排序的分類:

堆排序

堆排序基本介紹

1)堆排序是利用堆這種資料結構而設計的一種排序算法,堆排序是一種**選擇排序,**它的最壞,最好,平均時間複雜度均為O(nlogn),它也是不穩定排序。

2)堆是具有以下性質的完全二叉樹:每個結點的值都大于或等于其左右孩子結點的值,稱為大頂堆, 注意 :

沒有要求結點的左孩子的值和右孩子的值的大小關系。

3)每個結點的值都小于或等于其左右孩子結點的值,稱為小頂堆

4)大頂堆舉例說明

資料結構常見八大排序算法java實作詳細整理排序的分類:
資料結構常見八大排序算法java實作詳細整理排序的分類:

堆排序基本思想

堆排序的基本思想是:

1)将待排序序列構造成一個大頂堆

2)此時,整個序列的最大值就是堆頂的根節點。

3)将其與末尾元素進行交換,此時末尾就為最大值。

4)然後将剩餘n-1個元素重新構造成一個堆,這樣會得到n個元素的次小值。如此反複執行,便能得到一個有序序列了。

可以看到在建構大頂堆的過程中,元素的個數逐漸減少,最後就得到一個有序序列了.

代碼

package com.atguigu.tree;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class HeapSort {

	public static void main(String[] args) {
		//要求将數組進行升序排序
		int arr[] = {4, 6, 8, 5, 9};
		// 建立要給80000個的随機的數組
//		int[] arr = new int[8000000];
//		for (int i = 0; i < 8000000; i++) {
//			arr[i] = (int) (Math.random() * 8000000); // 生成一個[0, 8000000) 數
//		}
//
//		System.out.println("排序前");
//		Date data1 = new Date();
//		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//		String date1Str = simpleDateFormat.format(data1);
//		long time1 = System.currentTimeMillis();
//		System.out.println("排序前的時間是=" + date1Str);
//
		heapSort(arr);
//
//		Date data2 = new Date();
//		String date2Str = simpleDateFormat.format(data2);
//		System.out.println("排序前的時間是=" + date2Str);
//		long time2 = System.currentTimeMillis();
//		System.out.println(time2-time1);
		//System.out.println("排序後=" + Arrays.toString(arr));
	}

	//編寫一個堆排序的方法
	public static void heapSort(int arr[]) {
		int temp = 0;
		System.out.println("堆排序!!");
		
//		//分步完成
//		adjustHeap(arr, 1, arr.length);
//		System.out.println("第一次" + Arrays.toString(arr)); // 4, 9, 8, 5, 6
//		
//		adjustHeap(arr, 0, arr.length);
//		System.out.println("第2次" + Arrays.toString(arr)); // 9,6,8,5,4
		
		//完成我們最終代碼
		//将無序序列建構成一個堆,根據升序降序需求選擇大頂堆或小頂堆
		for(int i = arr.length / 2 -1; i >=0; i--) {
			adjustHeap(arr, i, arr.length);
		}
		
		/*
		 * 2).将堆頂元素與末尾元素交換,将最大元素"沉"到數組末端;
  			3).重新調整結構,使其滿足堆定義,然後繼續交換堆頂元素與目前末尾元素,反複執行調整+交換步驟,直到整個序列有序。
		 */
		for(int j = arr.length-1;j >0; j--) {
			//交換
			temp = arr[j];
			arr[j] = arr[0];
			arr[0] = temp;
			adjustHeap(arr, 0, j);
		}

		System.out.println("數組=" + Arrays.toString(arr));
		
	}
	
	//将一個數組(二叉樹), 調整成一個大頂堆
	/**
	 * 功能: 完成 将 以 i 對應的非葉子結點的樹調整成大頂堆
	 * 舉例  int arr[] = {4, 6, 8, 5, 9}; => i = 1 => adjustHeap => 得到 {4, 9, 8, 5, 6}  i=length/2 - 1;
	 * 如果我們再次調用  adjustHeap 傳入的是 i = 0 => 得到 {4, 9, 8, 5, 6} => {9,6,8,5, 4}
	 * @param arr 待調整的數組
	 * @param i 表示非葉子結點在數組中索引
	 * @param lenght 表示對多少個元素繼續調整, length 是在逐漸的減少
	 */
	public  static void adjustHeap(int arr[], int i, int lenght) {
		
		int temp = arr[i];//先取出目前元素的值,儲存在臨時變量
		//開始調整
		//說明
		//1. k = i * 2 + 1 k 是 i結點的左子結點
		for(int k = i * 2 + 1; k < lenght; k = k * 2 + 1) {
			if(k+1 < lenght && arr[k] < arr[k+1]) { //說明左子結點的值小于右子結點的值
				k++; // k 指向右子結點
			}
			if(arr[k] > temp) { //如果子結點大于父結點
				arr[i] = arr[k]; //把較大的值賦給目前結點
				i = k; //!!! i 指向 k,繼續循環比較
			} else {
				break;//!
			}
		}
		//當for 循環結束後,我們已經将以i 為父結點的樹的最大值,放在了 最頂(局部)
		arr[i] = temp;//将temp值放到調整後的位置
	}
	
}

           

常用排序算法總結和對比

資料結構常見八大排序算法java實作詳細整理排序的分類:

相關術語解釋:

1)穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面;

2)不穩定:如果a原本在b的前面,而a=b,排序之後a可能會出現在b的後面;

3)内排序:所有排序操作都在記憶體中完成;

4)外排序:由于資料太大,是以把資料放在磁盤中,而排序通過磁盤和記憶體的資料傳輸才能進行;

5)時間複雜度: 一個算法執行所耗費的時間。

6)空間複雜度:運作完一個程式所需記憶體的大小。

7)n**: 資料規模

8)k**: “桶“的個數

9)In-place**: 不占用額外記憶體

10)Out-place**:占用額外記憶體