堆排序演算法Java實現
阿新 • • 發佈:2021-08-29
介紹堆排序的基本概念及其實現。
讀後有收穫,小禮物走一走,請作者喝咖啡。
摘要 介紹堆排序的基本概念及其實現。
前言
排序大的分類可以分為兩種:內排序和外排序。在排序過程中,全部記錄存放在記憶體,則稱為內排序,如果排序過程中需要使用外存,則稱為外排序。這裡講的排序是內排序中的堆排序演算法,它屬於選擇排序的一種。
堆排序和插入排序一樣,是一種就地排序演算法(不需要額外的儲存空間)。堆是一種資料結構,它可以被視為一種完全二叉樹。最小堆又叫小頂堆,滿足小頂堆的條件是每個孩子節點的值都大於父節點。大頂堆則相反。
原始碼
import java.util.Arrays; public class HeapSort { //使用陣列儲存堆中的資料 private int[] data; public static void main(String[] args) { int[] a = {1, 50, 38, 78, 33, 12, 65, 97, 76, 13, 27, 32, 50, 63, 101}; int lastIndex = a.length - 1; //迴圈建堆 for (int i = 0; i < lastIndex; i++) { //建堆 buildMaxHeap(a, lastIndex - i); //交換堆頂和最後一個元素 swap(a, 0, lastIndex - i); System.out.println("第" + i + "次遍歷,執行結果:" + Arrays.toString(a)); } } /** * 對data陣列從0到 lastIndex 建大頂堆 */ public static 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 = left(k); //如果biggerIndex小於lastIndex,即biggerIndex+1代表的k節點的右子節點存在 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; } } } } /** * 根據父節點下標獲取左孩子節點下標 * * @param index 下標 * @return 2 * index + 1 */ private static int left(int index) { // 左移1位相當於乘2 return (index + 1) << 1 - 1; } //交換 private static void swap(int[] data, int i, int j) { int tmp = data[i]; data[i] = data[j]; data[j] = tmp; } }
基於Top k的最小堆演算法
/** * 最小堆 * */ public class MinHeap { //使用陣列儲存堆中的資料 private int[] data; public MinHeap(int[] data) { this.data = data; bulidHeap(); } /** * 建立最小堆 */ private void bulidHeap() { for (int i = (data.length) / 2 - 1; i >= 0; i--) {//下標小於等於i的節點擁有子節點 change(i); } } /** * 根據父節點判斷是否 * 與左右孩子交換 * * @param i */ private void change(int i) { int temp = 0; int left = left(i); int right = right(i); //存在右節點則存在左節點 if (right < data.length) { //拿到左右孩子中小的下標 temp = min(left, right); if (data[i] > data[temp]) { swap(i, temp); //如果和子節點發生交換,則要對子節點的左右孩子進行調整 change(temp); } } else if (right < data.length) { //不存在右節點但存在左節點,則左節點無孩子節點 if (data[i] > data[left]) swap(i, left); //孩子節點大於父節點,直接交換位置 } } /** * 獲取兩個節點中較小的節點的下標 * * @param i * @param j * @return */ private int min(int i, int j) { if (data[i] >= data[j]) return j; return i; } /** * 根據父節點下標獲取 * 左孩子節點下表 * * @param i * @return */ private int left(int i) { return ((i + 1) << 1) - 1; } /** * 根據父節點下表獲取 * 右孩子節點下標 * * @param i * @return */ private int right(int i) { return (i + 1) << 1;//左移1位相當於乘2 } /** * 根據下標交換陣列中的位置 * * @param i * @param j */ private void swap(int i, int j) { int temp = data[i]; data[i] = data[j]; data[j] = temp; } /** * 重置堆頂 * * @param root */ public void newRoot(int root) { data[0] = root; change(0); } /** * 獲取堆頂 * * @return */ public int getRoot() { return data[0]; } /** * 獲取堆資料 */ public int[] getData() { return data; } }
實戰
小頂堆和大頂堆對於解決TOP-K問題擁有較高的效率,在N個數中找到K(K<N)個最大或最小的數可以分別使用小頂堆演算法和大頂堆演算法。下面我用上面的小頂堆演算法找出一個數組中最大的7個數。
public class TopK { public static void main(String[] args) { int[] num = {1, 3, 6, 7, 332, 355, 11, 325, 63, 25, 75, 32, 393, 759}; int[] data = {1, 3, 6, 7, 332, 355, 11};//取前7個數據建立最小堆 MinHeap heap = new MinHeap(data); for (int i = 7; i < num.length; i++) { //迴圈與堆頂比較,大於於堆頂則重置堆頂 if (num[i] > heap.getRoot()) { heap.newRoot(num[i]); } } //迴圈輸出前7個最大的數 for (int n : heap.getData()) { System.out.println(n); } } }
測試結果如下所示:
63
325
75
393
332
355
759
小結
對於Wiener以上的話題,大家又有什麼自己的獨特見解呢?歡迎在下方評論區留言!
Reference
讀後有收穫,小禮物走一走,請作者喝咖啡。
讚賞支援