經典排序算法歸納

算法概述

算法分類

十種常見排序算法可以分爲兩大類:

  • 比較類排序:通過比較來決定元素間的相對次序,由於其時間複雜度不能突破O(nlogn),因此也稱爲非線性時間比較類排序。
  • 非比較類排序:不通過比較來決定元素間的相對次序,它可以突破基於比較排序的時間下界,以線性時間運行,因此也稱爲線性時間非比較類排序。

hhh

算法複雜度

image.png

相關概念

  • 穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面。
  • 不穩定:如果a原本在b的前面,而a=b,排序之後 a 可能會出現在 b 的後面。
  • 時間複雜度:對排序數據的總的操作次數。反映當n變化時,操作次數呈現什麼規律。
  • 空間複雜度:是指算法在計算機內執行時所需存儲空間的度量,它也是數據規模n的函數。

冒泡排序(Bubble Sort)

動圖演示

hh

Java實現

/**
 * 冒泡排序 平均時間複雜度爲o(n2),最好複雜度爲o(n),空間複雜度爲o(1)
 * 
 * @author monkJay
 */
public class BubbleSort {

	public static void solution(int[] array) {
		int len = array.length;
		// 從第一個開始直到倒數第二個
		for (int i = 0; i < len - 1; ++i) {
			// 每比較一輪,都會有一個已排序好的數字,i就是已經排序好了的個數
			for (int j = 1; j < len - i; ++j) {
				// 如果後面的數字小於前面的數字,則交換
				if (array[j] < array[j - 1]) {
					int tmp = array[j];
					array[j] = array[j - 1];
					array[j - 1] = tmp;
				}
			}
		}
	}

	public static void main(String[] args) {
		int[] array = new int[] { 2, 1, 4, 3, 6, 5, 9, 8 };
		solution(array);
		for (int i = 0; i < array.length; ++i) {
			System.out.print(array[i] + " ");
		}
	}
}

選擇排序(Selection Sort)

動圖演示

hsss

Java實現

/**
 * 選擇排序(最穩定的排序算法,不算什麼序列時間複雜度都是o(n2)) 每次從未排序的序列中選擇一個最小(大)的數放在已排序的序列的後面
 * 平均時間複雜度爲o(n2),最好複雜度爲o(n2),空間複雜度爲o(1)
 * 
 * @author monkJay
 */
public class SelectionSort {

	public static void solution(int[] array) {
		int len = array.length;
		for (int i = 0; i < len - 1; ++i) {
			int minIndex = i;
			for (int j = i + 1; j < len; ++j) {
				if (array[minIndex] > array[j]) {
					minIndex = j;
				}
			}
			int tmp = array[minIndex];
			array[minIndex] = array[i];
			array[i] = tmp;
		}
	}
}

插入排序(Insertion Sort)

動圖演示

dddas

Java實現

/**
 * 插入排序 | 選擇未排序序列的第一個數,插入到已排序序列中合適得位置(從後往前找,直到找到一個比當前數小或者等於的數,插在它後面)
 * 平均時間複雜度爲o(n2),最好複雜度爲o(n),空間複雜度爲o(1)
 * 
 * @author monkJay
 */
public class InsertionSort {

	public static void solution(int[] array) {
		int len = array.length;
		for (int i = 1; i < len; ++i) {
			int preIndex = i - 1;
			int current = array[i];
			while (preIndex >= 0 && array[preIndex] > current) {
				// 如果有序序列中的數大於要插入的數,則後移一個位置(將自己的位置空出來)
				array[preIndex + 1] = array[preIndex];
				// 繼續往前尋找
				--preIndex;
			}
			// 找到了適合的位置(終於找到一個小於等於要插入的數了),將數字插在這個位置後面
			array[preIndex + 1] = current;
		}
	}
}

希爾排序(Shell Sort)

動圖演示

sss

Java實現

/**
 * 希爾排序 將一個序列在邏輯上根據指定的步長分組,對每個分組進行插入排序(插入排序時的移動是根據當前步長移動查找的)
 * 
 * @author monkJay
 */
public class ShellSort {

	public static void solution(int[] array) {
		int len = array.length;
		// 步長從當前長度的一半開始,每一輪將步長減爲一半,直到最後小於1結束
		for (int gap = len / 2; gap > 0; gap /= 2) {
			// 取值從步長開始,這裏其實就是將直接插入排序中的1全部換成了步長gap
			// 其它和直接插入排序一樣
			for (int i = gap; i < len; ++i) {
				int preIndex = i - gap;
				int current = array[i];
				while (preIndex >= 0 && array[preIndex] > current) {
					array[preIndex + gap] = array[preIndex];
					preIndex = preIndex - gap;
				}
				array[preIndex + gap] = current;
			}
		}
	}
}

歸併排序(Merge Sort)

動圖演示

ddsds

Java實現

/**
 * 二路歸併排序 用到了分治的思想,不斷往下細分,分到只有一個數字(認爲是有序的)的時候開始合併,最後合併爲一個有序的數組
 * 
 * @author monkJay
 */
public class MergeSort {

	public static void solution(int[] array) {
		int len = array.length;
		if (len == 0) {
			return;
		}
		int left = 0;
		int right = len - 1;
		int[] tmp = new int[len];
		mergeSort(array, tmp, left, right);
	}

	public static void mergeSort(int[] array, int[] tmp, int left, int right) {
		// 如果還可以分組,其實最後是分到只有一個數(認爲一個數是有序的)
		if (left < right) {
			int center = left + (right - left) / 2;
			// 左邊的數組繼續往下分
			mergeSort(array, tmp, left, center);
			// 右邊的數組繼續往下分
			mergeSort(array, tmp, center + 1, right);
			// 將左右兩個有序的數組合並
			merge(array, tmp, left, center, right);
		}
	}

	public static void merge(int[] array, int[] tmp, int left, int center,
			int right) {
		int i = left, j = center + 1;
		// 將要合併的兩個有序數組,從小到大有序的放在輔助數組中
		for (int k = left; k <= right; ++k) {
			// 如果左邊的數組遍歷完了,就直接放右邊的數組
			if (i > center) {
				tmp[k] = array[j++];
			}
			// 如果右邊的數組遍歷完了,就直接放左邊的數組
			else if (j > right) {
				tmp[k] = array[i++];
			}
			// 將小的放在輔助數組中
			else if (array[i] <= array[j]) {
				tmp[k] = array[i++];
			} else {
				tmp[k] = array[j++];
			}
		}
		// 將輔助數組中的值複製到原數組中
		for (int k = left; k <= right; ++k) {
			array[k] = tmp[k];
		}
	}
}

快速排序(Quick Sort)

動圖演示

ss

Java實現

/**
 * 快速排序
 * 通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,
 * 則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
 * @author monkJay
 */
public class QuickSort {

	public static void solution(int[] array) {
		int len = array.length;
		if (len == 0) {
			return;
		}
		int left = 0, right = len - 1;
		quickSort(array, left, right);
	}

	public static void quickSort(int[] array, int left, int right) {
		if (left < right) {
			int partitionIndex = partition(array, left, right);
			quickSort(array, left, partitionIndex - 1);
			quickSort(array, partitionIndex + 1, right);
		}
	}

    // 第一種分區方法
	public static int partition(int[] array, int left, int right) {
        // 選取左邊第一個數作爲中軸數
		int pivot = left;
        // 從中軸數後面一個位置開始比較
		int index = pivot + 1;
        // 保證指針左邊的數都是小於中軸數的
		for (int i = left + 1; i <= right; i++) {
            // 如果小於中軸數,將當前數的位置和中軸指針交換
			if (array[i] < array[pivot]) {
				swap(array, i, index);
                // 指針繼續後移
				++index;
			}
		}
        // 將中軸數的位置和指針左邊一個位置交換,也就是把中軸數換到中間
		swap(array, pivot, index - 1);
        // 最後返回這個中軸數的位置
		return index - 1;
	}
    // 第二種分區方法
	// public static int partition(int[] array, int left, int right) {
	// int i = left, j = right + 1;
	// int pivot = array[left];
	// while (i < j) {
	//
	// while (array[--j] > pivot && i < j)
	// ;
	// while (array[++i] < pivot && i < j)
	// ;
	// if (i < j) {
	// swap(array, i, j);
	// }
	// }
	// swap(array, left, j);
	// return j;
	// }

	public static void swap(int[] array, int a, int b) {
		int tmp = array[a];
		array[a] = array[b];
		array[b] = tmp;
	}
}

堆排序(Heap Sort)

動圖演示

sds

Java實現

/**
 * 堆排序 先構建一個最大(小)堆,每次都堆頂取一個數,並把最後一個數交換到堆頂,然後重新調整堆,重複,直到堆中最後一個數
 * 最大堆得到從小到大的排序,最小堆得到從大到小的排序
 * 
 * @author HP
 *
 */
public class HeapSort {

	public static void solution(int[] array) {
		int len = array.length;
		if (len == 0) {
			return;
		}
		buildHeap(array);
		heapSort(array);
	}

	/**
	 * 下沉調整 用於堆的刪除,還可用於構建二叉堆
	 * 
	 * @param array 存放堆的數組
	 * @param parentIndex 父結點位置
	 * @param len 數組長度
	 */
	public static void downAdjust(int[] array, int parentIndex, int len) {
		// 先計算得到左孩子
		int childIndex = parentIndex * 2 + 1;
		// 保存父結點的值
		int tmp = array[parentIndex];
		while (childIndex < len) {
			// 如果右孩子存在,且右孩子的值比左孩子要小,則將孩子節點定位到右孩子
			if (childIndex + 1 < len
					&& array[childIndex + 1] < array[childIndex]) {
				childIndex++;
			}
			// 如果父結點的值比兩個孩子都小,不用調整了,直接結束
			if (tmp < array[childIndex]) {
				break;
			}
			// 將孩子節點的值賦給父結點,繼續下沉
			array[parentIndex] = array[childIndex];
			// 將孩子節點作爲新的父結點
			parentIndex = childIndex;
			// 重新計算左孩子的位置
			childIndex = parentIndex * 2 + 1;
		}
		// 節點調整結束,將最初需要調整的那個節點賦值到最終位置
		array[parentIndex] = tmp;
	}

	/**
	 * 其實就是讓每個非葉子節點依次下沉
	 * 構造堆 時間複雜度是線性的
	 * @param array 要構造堆的數組
	 */
	public static void buildHeap(int[] array) {
		int len = array.length;
		// 找到最後一個節點的父結點
		int startIndex = (len - 2) / 2;
		for (int i = startIndex; i >= 0; --i) {
			downAdjust(array, i, len);
		}
	}

	/**
	 * 堆排序 每次都堆頂取一個數,並把最後一個數交換到堆頂,然後重新調整堆,重複,直到堆中最後一個數
	 * @param array 
	 */
	public static void heapSort(int[] array) {
		for (int i = array.length - 1; i > 0; --i) {
			int tmp = array[0];
			array[0] = array[i];
			array[i] = tmp;
			// 從堆頂開始調整
			downAdjust(array, 0, i);
		}
	}
}

計數排序(Counting Sort)

動圖演示

sss

Java實現

/**
 * 計數排序
 * 計數排序不是基於比較的排序算法,其核心在於將輸入的數據值轉化爲鍵存儲在額外開闢的數組空間中。
 * 作爲一種線性時間複雜度的排序,計數排序要求輸入的數據必須是有確定範圍的整數。
 * @author HP
 */
public class CountingSort {

	public static void solution(int[] array) {
		int len = array.length;
		if (len == 0) {
			return;
		}
		int maxValue = 0;
        // 得到最大值
		for (int i = 0; i < len; ++i) {
			if (array[i] > maxValue) {
				maxValue = array[i];
			}
		}
		int[] bucket = new int[maxValue + 1];
		// 將桶用0填充
		Arrays.fill(bucket, 0);
		// 記錄每個元素出現的個數
		for (int i = 0; i < len; ++i) {
			bucket[array[i]]++;
		}
		int index = 0;
		for (int i = 0; i < bucket.length; ++i) {
            // 將桶中的元素往原數組放
			while (bucket[i] > 0) {
				array[index++] = i;
				bucket[i]--;
			}
		}
	}
}

topK的四種解法

構造大頂堆

算法分析:用數組前k個數構造一個大頂堆,然後依次比較後面的數,如果小於堆頂,則換掉,重新調整堆,最後的堆頂元素就是第k大的元素。時間複雜度爲O(klogk)。
Java實現

public class TopK {

	public static int topK_Solution(int[] input, int k) {
		int len = input.length;
		if (len == 0 || k <= 0 || k > len) {
			return 0;
		}
        // 先把前k個數構造成一個堆
		int[] array = Arrays.copyOfRange(input, 0, k);
		buildHeap(array);
        // 遍歷後面的數,如果小於堆頂元素,就把堆頂換掉,並重新下沉調整堆
		for (int i = k; i < len; ++i) {
			if (input[i] < array[0]) {
				array[0] = input[i];
				downAdjust(array, 0, array.length);
			}
		}
		return array[0];
	}

    // 構造堆
	public static void buildHeap(int[] array) {
		int startIndex = (array.length - 2) / 2;
		for (int i = startIndex; i >= 0; --i) {
			downAdjust(array, i, array.length);
		}
	}

	// 調整最大堆
	public static void downAdjust(int[] array, int parentIndex, int len) {
		int tmp = array[parentIndex];
		int childIndex = parentIndex * 2 + 1;
		while (childIndex < len) {
			if (childIndex + 1 < len
					&& array[childIndex + 1] > array[childIndex]) {
				childIndex++;
			}
			if (tmp > array[childIndex]) {
				break;
			}
			array[parentIndex] = array[childIndex];
			parentIndex = childIndex;
			childIndex = parentIndex * 2 + 1;
		}
		array[parentIndex] = tmp;
	}

	public static void main(String[] args) {
		int[] array = new int[] { 2, 1, 4, 3, 6, 5, 7, 9, 8 };
		int res = GetLeastNumbers_Solution(array, 5);
		System.out.print("第K大的元素是:" + res);
	}
}

構造小頂堆

算法分析:先把數組構造成小頂堆,然後對堆進行k - 1次刪除堆頂的操作,最後的堆頂就是要求的第k大元素 。時間複雜度爲O(klogN)。

public class Solution {
    public int topK_Solution(int [] input, int k) {
        int len = input.length;
        if (len == 0 || k <= 0 || k > len){
            return 0;
        }
        // 先構造一個堆
        bulidHeap(input);
        // 將數組進行k - 1次刪除堆頂操作
        for (int i = len - 1; i > len - k; --i){
            int tmp = input[0];
            input[0] = input[i];
            input[i] = tmp;
            downAdjust(input, 0, i);
        }
        // 返回最後的堆頂
        return input[0];
    }
    public void bulidHeap(int[] array){
        int startIndex = (array.length - 2) / 2;
        for (int i = startIndex; i >= 0; --i){
            downAdjust(array, i, array.length);
        }
    }
     public void downAdjust(int[] array, int parentIndex, int len){
            int tmp = array[parentIndex];
            int childIndex = parentIndex * 2 + 1;
            while (childIndex < len){
                if (childIndex + 1 < len && array[childIndex + 1] < array[childIndex]){
                    childIndex ++;
                }
                if (tmp < array[childIndex]){
                    break;
                }
                array[parentIndex] = array[childIndex];
                parentIndex = childIndex;
                childIndex = parentIndex * 2 + 1;
            }
            array[parentIndex] = tmp;
        }
}

使用Java的優先隊列

算法分析:其實跟前面用堆是一樣的,只不過用了Java中已經封裝好的堆,代碼更簡潔(但這裏貌似耗時要長一點,其實這裏刪除堆頂不用調整,但它這裏每次都調整了,所以浪費了時間)

public class Solution {
    public int topK_Solution(int [] input, int k) {
        int len = input.length;
        if (len == 0 || k <= 0 || k > len){
            return 0;
        }
        // 使用優先隊列構造一個最大堆,這裏用了lambda表達式
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, (o1, o2) -> {
            return o2.compareTo(o1);
        });
        for (int i = 0; i < len; ++i) {
            // 往堆中添加數字,直到堆的大小爲k
            if (maxHeap.size() != k) {
                maxHeap.offer(input[i]);
            }
            // 如果當前元素比堆頂的元素要小,那麼將堆頂出隊列,將當前元素加入隊列(內部會自動調整堆)
            else if (input[i] < maxHeap.peek()) {
                maxHeap.poll();
                maxHeap.offer(input[i]);
            }
        }
        // 返回堆頂元素
        return maxHeap.peek();
    }
}

利用快排的思想

算法分析:基於數組的第k個數來調整,使得最後小於k的數都在k的左邊,大於k的數都在k的右邊,那麼k就是第k大元素

public class Solution {
    public int topK_Solution(int [] input, int k) {
        int len = input.length;
        if (len == 0 || k <= 0 || k > len){
            return 0;
        }
        int start = 0;
        int end = len - 1;
        int index = partition(input, start, end);
        while (index != k - 1){
            if (index > k - 1){
                end = index - 1;
                index = partition(input, start, end);
            } else {
                start = index + 1;
                index = partition(input, start, end);
            }
        }
        return input[k - 1];
    }
    public int partition(int[] input, int left, int right){
        int pivot = left;
        int index = pivot + 1;
        for (int i = left; i <= right; ++i){
            if (input[i] < input[pivot]){
                swap(input, i, index);
                ++index;
            }
        }
        swap(input, pivot, index - 1);
        return index - 1;
    }
    public void swap(int[] input, int a, int b){
        int tmp = input[a];
        input[a] = input[b];
        input[b] = tmp;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章