您現在的位置是:首頁 > 棋牌

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

  • 由 Java0那點事兒0 發表于 棋牌
  • 2021-12-17
簡介}}private static void adjustHeap(int[] arr, int parentIndex, int length) {temp儲存父節點的值int temp=arr[parentIndex]

堆頂怎麼調整

1。堆排序

1。1二叉堆概念以及原理

二叉堆本質上是一種完全二叉樹,它分為兩個型別:大頂堆和小頂堆

大頂堆(最大堆)

最大堆的任何一個父節點的值,都大於或等於它左、右孩子節點的值。

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

小頂堆(最小堆)

最小堆的任何一個父節點的值,都小於或等於它左、右孩子節點的值。

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

二叉堆的根節點叫作

堆頂

,最大堆和最小堆的特點決定了:最大堆的堆頂是整個堆中的最大元素;最小堆的堆頂是整個堆中的最小元素。

儲存原理

完全二叉樹比較適合用

陣列

來儲存。用陣列來儲存完全二叉樹是非常節省儲存空間的。因為我們不需要儲存左右子節點的指標,

單純地透過陣列的下標,就可以找到一個節點的左右子節點和父節點

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

陣列中下標為

i 的節點的左子節點,就是下標為 i2 的節點,右子節點就是下標為 i2+1 的節點

父節點就是下標為 i/2 取整的節點

(儲存資料從陣列下標1開始的,也可以從0開始)

1。2 堆排序

陣列從邏輯上講就是一個堆結構,我們用簡單的公式來描述一下堆的定義就是:

大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] 2*(i+1)

小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

堆排序的基本思想

將待排序序列構造成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點。將其與末尾元素進行交換,此時末尾就為最大值。然後將剩餘n-1個元素重新構造成一個堆,這樣會得到n個元素的次小值。如此反覆執行,便能得到一個有序序列了。

排序過程如下:

1。將給定無序序列構造成一個大頂堆(升序用大頂堆,降序用小頂堆)

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

2。然後從最後一個非葉子節點開始(

最後一個非葉子節點 的index=arr.length/2-1

),從左至右,從下至上依次次調整。

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

3。找到第二個非葉節點4,由於[4,9,8]中9元素最大,4和9交換

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

4。這時,交換導致了子根[4,5,6]結構混亂,繼續調整,[4,5,6]中6最大,交換4和6

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

5。將堆頂元素與末尾元素進行交換,使末尾元素最大。然後繼續調整堆,再將堆頂元素與末尾元素交 換,得到第二大元素。如此反覆進行交換、重建、交換

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

重新調整結構,使其繼續滿足堆定義

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

再將堆頂元素8與末尾元素5進行交換,得到第二大元素8

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

如此反覆最終使得整個序列有序。

1。3 堆排序程式碼實現

public class HeapSort {

public static void sort(int[] arr){

//1。構建大頂堆

for (int i=arr。length/2-1;i>=0;i——){

//從最後一個非葉子節點開始

adjustHeap(arr,i,arr。length);

}

//2。大頂堆首尾元素交換,重複構建

for (int i = arr。length - 1; i > 0; i——){

// 最後1個元素和第1個元素進行交換

int temp = arr[i];

arr[i] = arr[0];

arr[0] = temp;

// “下沉”調整最大堆

adjustHeap(arr, 0, i);

}

}

private static void adjustHeap(int[] arr, int parentIndex, int length) {

//temp儲存父節點的值

int temp=arr[parentIndex];

//左子節點的index

int childIndex=2*parentIndex+1;

while (childIndex

//此邏輯是獲取2個子節點的最大的一個,如果不走if 說明左子節點大,反之右節點大

if (childIndex+1arr[childIndex]){

//如果存在右子節點,而且右子節點比左子節點大

childIndex++;

}

//如果temp大就不用比 直接跳出

if (temp>arr[childIndex]) break;

//交換

arr[parentIndex]=arr[childIndex];

//將當前最大子節點的index 作為父節點繼續迴圈

parentIndex=childIndex;

//下一個孩子節點

childIndex=2*childIndex+1;

}

arr[parentIndex] = temp;

}

public static void main(String []args){

int []arr = {7,6,4,3,5,2,10,9,8};

System。out。println(“排序前:”+ Arrays。toString(arr));

sort(arr);

System。out。println(“排序後:”+ Arrays。toString(arr));

}

}

測試輸出:

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

小結:

時間複雜度:O(nlogn)

跟快排時間複雜度是一樣的,但是這個思路感覺相對複雜,程式碼邏輯需要從前面排序原理對比著來看,才能看出每一步的作用。

1。4 堆的應用

優先佇列,求Top K等等。

2。計數排序

2。1概念和原理

計數排序,這種排序演算法是利用陣列下標來確定元素的正確位置的,這種排序的侷限性比較大,適合於連續的取值範圍不大的陣列(通常都是整數)。

排序原理過程

假設陣列中有10個整數,取值範圍為0~10,可以根據有限的範圍,建一個長度為11的陣列,陣列下標從0到10,元素初始值為0。

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

假設待排序的陣列為:9,1,2,7,8,1,3,6,5,3

遍歷這個陣列,每個元素的值對應的新建陣列的下標+1,例如第一個數是9,那麼陣列下標9的數值+1,

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

依次遍歷下去,最終得到的陣列資料為:

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

然後再遍歷陣列,輸出元素下標,下標對應的數值是多少,就輸出幾次,輸出結果為:1、1、2、3、3、5、6、7、8、9 。

注意

不連續和取值範圍過大會造成陣列過大

,如果起始不是0,比如考試分數,可以採取偏移量的方式來節約陣列空間。

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

2。2 程式碼實現

以考試分數 90分以上的分數的陣列作為案例來實現計數排序。

public class CountSort {

public static int[] sort(int[] arr,int offset){

int[] temp=new int[11];

//計數

for (int n : arr) {

int i=n-offset;

temp[i]++;

}

//填充新陣列

int[] result=new int[arr。length];

// i是計數陣列下標,k是新陣列下標

for (int i = 0, k = 0; i < temp。length; i++) {

for (int j = 0; j < temp[i]; j++) {

result[k++] = i + offset;

}

}

return result;

}

public static void main(String[] args) {

int[] scores = {95, 94, 91, 98, 99, 90, 99, 93, 91, 92};

System。out。println(“排序前的陣列:”+ Arrays。toString(scores));

int[] sort = sort(scores, 90);

System。out。println(“排序後的陣列:”+ Arrays。toString(sort));

}

}

測試輸出:

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

小結:

時間複雜度 O(m+n),在特定情況下使用計數排序,效率還是相當不錯的。

3。 桶排序

3。1 概念和原理

桶排序也是一種線性時間的排序演算法,桶排序需要建立若干個桶來協助排序,每一個桶(bucket)代表一個區間範圍,裡面可以承載一個或多個元素。

排序過程

第1步,建立這些桶,並確定每一個桶的區間範圍具體需要建立多少個桶

確定桶的區間範圍,有很多種不同的方式。可以選擇:

建立的桶數量等於原始數列的元素數量

,除最後一個桶只包 含數列最大值外, 前面各個桶的區間按照比例來確定。

區間跨度 = (最大值-最小值)/ (桶的數量 - 1)

假設有一個非整數數列:4。5,0。84,3。25,2。18,0。5

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

第2步,遍歷原始數列,把元素對號入座放入各個桶中

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

第3步,對每個桶內部的元素分別進行排序(這裡只需對第一個桶進行排序)

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

第4步,遍歷所有的桶,輸出所有元素

0。5,0。84,2。18,3。25,4。5

3。2 程式碼實現

public class BucketSort {

public static double[] bucketSort(double[] array) {

double max = 0;

double min = 0;

//獲得最大值和最小值之間的差

for (int i = 0; i < array。length; i++) {

if (array[i] > max) {

max = array[i];

} if (array[i] < min) {

min = array[i];

}

}

double d = max - min;

//桶初始化

int bucketNum = array。length;

ArrayList> bucketList =new ArrayList<>(bucketNum);

for (int i = 0; i < bucketNum; i++) {

bucketList。add(new LinkedList<>());

} //將每個元素放入桶中

for (int i = 0; i < array。length; i++) {

int num = (int) ((array[i] - min) * (bucketNum - 1) / d);

bucketList。get(num)。add(array[i]);

} //對每個桶內部進行排序

for (int i = 0; i < bucketList。size(); i++) {

Collections。sort(bucketList。get(i));

} //輸出全部元素

double[] sortedArray = new double[array。length];

int index = 0;

for (LinkedList list : bucketList) {

for (double element : list) {

sortedArray[index] = element;

index++;

}

}

return sortedArray;

}

public static void main(String[] args) {

double[] array = {4。12, 6。421, 0。0023, 3。0, 2。123, 8。122, 4。12, 10。09};

System。out。println(“排序前:”+Arrays。toString(array));

double[] sortedArray = bucketSort(array);

System。out。println(“排序後:”+Arrays。toString(sortedArray));

}

}

測試效果:

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

時間複雜度:O(n)

3。3 幾種排序對比

Java資料結構與演算法之排序(堆排序、計數排序、桶排序)

Top