欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

堆排序原理及算法代碼詳解

 更新時(shí)間:2021年08月11日 14:46:49   作者:抽離的心  
這篇文章主要介紹了堆排序算法的講解及Java版實(shí)現(xiàn),堆排序基于堆這種數(shù)據(jù)結(jié)構(gòu),在本文中對(duì)堆的概念也有補(bǔ)充介紹,需要的朋友可以參考下

一、堆排序算法原理和動(dòng)態(tài)圖解

將待排序的序列構(gòu)造成一個(gè)大頂堆。此時(shí),整個(gè)序列的最大值就是堆頂?shù)母?jié)點(diǎn)。將它移走(其實(shí)就是將其與堆數(shù)組的末尾元素交換,此時(shí)末尾元素就是最大值),然后將剩余的n-1個(gè)序列重新構(gòu)造成一個(gè)堆,這樣就會(huì)得到n個(gè)元素中的次最大值。如此反復(fù)執(zhí)行,就能得到一個(gè)有序序列了。這個(gè)過程其實(shí)就是先構(gòu)建一個(gè)最大/最小二叉堆,然后不停的取出最大/最小元素(頭結(jié)點(diǎn)),插入到新的隊(duì)列中,以此達(dá)到排序的目的。如下圖所示:

二、二叉樹定義

要了解堆首先得了解一下二叉樹,在計(jì)算機(jī)科學(xué)中,二叉樹是每個(gè)節(jié)點(diǎn)最多有兩個(gè)子樹的樹結(jié)構(gòu)。通常子樹被稱作“左子樹”(left subtree)和“右子樹”(right subtree)。二叉樹常被用于實(shí)現(xiàn)二叉查找樹和二叉堆。二叉樹的每個(gè)結(jié)點(diǎn)至多只有二棵子樹(不存在度大于 2 的結(jié)點(diǎn)),二叉樹的子樹有左右之分,次序不能顛倒。二叉樹的第 i 層至多有 2i - 1 個(gè)結(jié)點(diǎn);深度為 k 的二叉樹至多有 2k - 1 個(gè)結(jié)點(diǎn);對(duì)任何一棵二叉樹 T,如果其終端結(jié)點(diǎn)數(shù)為 n0,度為 2 的結(jié)點(diǎn)數(shù)為 n2,則n0 = n2 + 1。二叉樹又分為完全二叉樹(complete binary tree)和滿二叉樹(full binary tree)。樹和二叉樹的三個(gè)主要差別:

  • 樹的結(jié)點(diǎn)個(gè)數(shù)至少為 1,而二叉樹的結(jié)點(diǎn)個(gè)數(shù)可以為 0
  • 樹中結(jié)點(diǎn)的最大度數(shù)沒有限制,而二叉樹結(jié)點(diǎn)的最大度數(shù)為 2
  • 樹的結(jié)點(diǎn)無左、右之分,而二叉樹的結(jié)點(diǎn)有左、右之分

1.滿二叉樹:一棵深度為 k,且有 2k - 1 個(gè)節(jié)點(diǎn)稱之為滿二叉樹,即每一層上的節(jié)點(diǎn)數(shù)都是最大節(jié)點(diǎn)數(shù)。如下圖b所示:深度為3的滿二叉樹。

2.完全二叉樹:而在一棵二叉樹中,除最后一層外,若其余層都是滿的,并且最后一層或者是滿的,或者是在右邊缺少連續(xù)若干節(jié)點(diǎn),則此二叉樹為完全二叉樹(Complete Binary Tree)。如下圖a所示:是一個(gè)深度為4的完全二叉樹。

三、堆的定義

堆(二叉堆)可以視為一棵完全的二叉樹,完全二叉樹的一個(gè)“優(yōu)秀”的性質(zhì)是,除了最底層之外,每一層都是滿的,這使得堆可以利用數(shù)組來表示(普通的一般的二叉樹通常用鏈表作為基本容器表示),每一個(gè)結(jié)點(diǎn)對(duì)應(yīng)數(shù)組中的一個(gè)元素。

對(duì)于7在數(shù)組存放的position=2,而它的子元素6的position=5=2*2[也就是父元素存放的位置]+1、子元素4的position=6=2*2[也就是父元素存放的位置]+2;同樣對(duì)于11在在數(shù)組存放的position=0,而它的子元素10的position=1=2*0[也就是父元素存放的位置]+1、子元素7的position=2=2*0[也就是父元素存放的位置]+2;所以對(duì)于i個(gè)元素,它的左右子節(jié)點(diǎn)在下標(biāo)以0開始的數(shù)組中的位置分別為:2*i+1、2*i+2。那腦補(bǔ)一下,對(duì)于不完全二叉樹,如果用數(shù)組來存放會(huì)有什么問題呢?當(dāng)然是中間有很多空的元素啦,所以說對(duì)于不完全二叉樹最好是用鏈表來存儲(chǔ)~。

堆的構(gòu)建過程示例:建堆的核心內(nèi)容是調(diào)整堆,使二叉樹滿足堆的定義(每個(gè)節(jié)點(diǎn)的值都不大于其父節(jié)點(diǎn)的值)。調(diào)堆的過程應(yīng)該從最后一個(gè)非葉子節(jié)點(diǎn)開始,假設(shè)有數(shù)組A = {1, 3, 4, 5, 7, 2, 6, 8, 0}。那么調(diào)堆的過程如下圖,數(shù)組下標(biāo)從0開始,A[3] = 5開始。分別與左孩子和右孩子比較大小,如果A[3]最大,則不用調(diào)整,否則和孩子中的值最大的一個(gè)交換位置,在圖1中是A[7] > A[3] > A[8],所以A[3]與A[7]對(duì)換,從圖1.1轉(zhuǎn)到圖1.2。

二叉堆(英語:binary heap)是一種特殊的堆,二叉堆是完全二叉樹或者是近似完全二叉樹。二叉堆滿足堆特性:父節(jié)點(diǎn)的鍵值總是保持固定的序關(guān)系于任何一個(gè)子節(jié)點(diǎn)的鍵值,且每個(gè)節(jié)點(diǎn)的左子樹和右子樹都是一個(gè)二叉堆。當(dāng)父節(jié)點(diǎn)的鍵值總是大于或等于任何一個(gè)子節(jié)點(diǎn)的鍵值時(shí)為最大堆。 當(dāng)父節(jié)點(diǎn)的鍵值總是小于或等于任何一個(gè)子節(jié)點(diǎn)的鍵值時(shí)為最小堆。二叉堆一般用數(shù)組來表示。如果根節(jié)點(diǎn)在數(shù)組中的位置是1,第n個(gè)位置的子節(jié)點(diǎn)分別在2n和 2n+1。因此,第1個(gè)位置的子節(jié)點(diǎn)在2和3,第2個(gè)位置的子節(jié)點(diǎn)在4和5。以此類推。這種基于1的數(shù)組存儲(chǔ)方式便于尋找父節(jié)點(diǎn)和子節(jié)點(diǎn)。如果存儲(chǔ)數(shù)組的下標(biāo)基于0,那么下標(biāo)為i的節(jié)點(diǎn)的子節(jié)點(diǎn)是2i + 1與2i + 2;其父節(jié)點(diǎn)的下標(biāo)是⌊floor((i − 1) ∕ 2)⌋。函數(shù)floor(x)的功能是“向下取整”,或者說“向下舍入”,即取不大于x的最大整數(shù)(與“四舍五入”不同,向下取整是直接取按照數(shù)軸上最接近要求值的左邊值,即不大于要求值的最大的那個(gè)值)。比如floor(1.1)、floor(1.9)都返回1。對(duì)于堆定義中的堆結(jié)構(gòu)插入元素:對(duì)于二叉堆來說,要插入一個(gè)新元素其整個(gè)過程是怎么樣的呢?這里還是以我們之前的那個(gè)二叉堆進(jìn)行說明,以插入"9"為例:

目前肯定不滿足二叉堆的要求,父接點(diǎn)6是小于新插入的節(jié)點(diǎn)9的,所以兩者進(jìn)行位置交換:

同樣的思路,父節(jié)點(diǎn)7比子節(jié)點(diǎn)9要小,所以需要調(diào)換位置:

至此元素插入完成,也符合二叉堆父元素大于子元素的規(guī)則,從添加過程中可以發(fā)現(xiàn):只需更改待比較的元素,其它的任何元素位置不需要?jiǎng)?,所以效率還是很高的。對(duì)于堆定義中的堆結(jié)構(gòu)刪除元素:這里以刪除根結(jié)點(diǎn)為例【因?yàn)閯h除根節(jié)點(diǎn)是最重要的,所以以它為例】,整個(gè)過程如下:

這時(shí)當(dāng)然是不符合二叉堆的規(guī)則,接著這樣來做:

同理繼續(xù)進(jìn)行處理:

繼續(xù):

經(jīng)過這些動(dòng)作之后就將一個(gè)根結(jié)點(diǎn)給刪除掉了,可以發(fā)現(xiàn)其實(shí)跟插入一個(gè)元素一樣,只需更改待比較的元素,其它的任何元素位置不需要?jiǎng)?,那像這種每次移除掉最大的值有啥用呢?堆排序就產(chǎn)生了,因?yàn)槊看螐母?jié)點(diǎn)拿肯定是最大的數(shù)【以最大堆來說】,這樣拿出來的數(shù)就成了一個(gè)有序的數(shù)列了。注意:對(duì)于一個(gè)很大的堆,這種存儲(chǔ)是低效的。因?yàn)楣?jié)點(diǎn)的子節(jié)點(diǎn)很可能在另外一個(gè)內(nèi)存頁中。B-heap是一種效率更高的存儲(chǔ)方式,把每個(gè)子樹放到同一內(nèi)存頁。如果用指針鏈表存儲(chǔ)堆,那么需要能訪問葉節(jié)點(diǎn)的方法??梢詫?duì)二叉樹“穿線”(threading)方式,來依序遍歷這些節(jié)點(diǎn)。

四、堆排序Java代碼實(shí)現(xiàn)

package com.luna.sort;
public class HeapSortMaxAndMin{
	public static void main(String[] args) {
		int[] array = { 19, 38, 7, 36, 5, 5, 3, 2, 1, 0, 56 };
		System.out.println("排序前:");
		for (int i = 0; i < array.length; i++) {
			System.out.print(array[i] + ",");
		}
		System.out.println();
		System.out.println("分割線---------------");
		heapSort(array);
		System.out.println("排序后:");
		for (int i = 0; i < array.length; i++) {
			System.out.print(array[i] + ",");
		}
	}
	public static void heapSort(int[] array) {
		if (array == null || array.length == 1)
			return;
		buildArrayToHeap(array); //將數(shù)組元素轉(zhuǎn)化為大頂堆/小頂堆
		for (int i = array.length - 1; i >= 1; i--) {
			// 經(jīng)過上面的一些列操作,目前array[0]是當(dāng)前數(shù)組里最大的元素,需要和末尾的元素交換,然后拿出最大的元素
			swap(array, 0, i);
			/**
			 * 交換完后,下次遍歷的時(shí)候,就應(yīng)該跳過最后一個(gè)元素,也就是最大的那個(gè)
			 * 值,然后開始重新構(gòu)建最大堆堆的大小就減去1,然后從0的位置開始最大堆
			 */
//			buildMaxHeap(array, i, 0);
			buildMinHeap(array, i, 0);
		}
	}
	// 構(gòu)建堆
	public static void buildArrayToHeap(int[] array) {
		if (array == null || array.length == 1)
			return;
		//遞推公式就是 int root = 2*i, int left = 2*i+1, int right = 2*i+2;
		int cursor = array.length / 2;
		for (int i = cursor; i >= 0; i--) { // 這樣for循環(huán)下,就可以第一次排序完成
//			buildMaxHeap(array, array.length, i);
			buildMinHeap(array, array.length, i);
		}
	}
	//大頂堆
	public static void buildMaxHeap(int[] array, int heapSieze, int index) {
		int left = index * 2 + 1; // 左子節(jié)點(diǎn)
		int right = index * 2 + 2; // 右子節(jié)點(diǎn)
		int maxValue = index; // 暫時(shí)定在Index的位置就是最大值
		// 如果左子節(jié)點(diǎn)的值,比當(dāng)前最大的值大,就把最大值的位置換成左子節(jié)點(diǎn)的位置
		if (left < heapSieze && array[left] > array[maxValue]) {
			maxValue = left;
		}
		// 如果右子節(jié)點(diǎn)的值,比當(dāng)前最大的值大,就把最大值的位置換成右子節(jié)點(diǎn)的位置
		if (right < heapSieze && array[right] > array[maxValue]) {
			maxValue = right;
		}
		// 如果不相等說明,這個(gè)子節(jié)點(diǎn)的值有比自己大的,位置發(fā)生了交換了位置
		if (maxValue != index) {
			swap(array, index, maxValue); // 就要交換位置元素
			// 交換完位置后還需要判斷子節(jié)點(diǎn)是否打破了最大堆的性質(zhì)。最大堆性質(zhì):兩個(gè)子節(jié)點(diǎn)都比父節(jié)點(diǎn)小。
			buildMaxHeap(array, heapSieze, maxValue);
		}
	}
	//小頂堆
	public static void buildMinHeap(int[] array, int heapSieze, int index) {
		int left = index * 2 + 1; // 左子節(jié)點(diǎn)
		int right = index * 2 + 2; // 右子節(jié)點(diǎn)
		int maxValue = index; // 暫時(shí)定在Index的位置就是最小值
		// 如果左子節(jié)點(diǎn)的值,比當(dāng)前最小的值小,就把最小值的位置換成左子節(jié)點(diǎn)的位置
		if (left < heapSieze && array[left] < array[maxValue]) {
			maxValue = left;
		}
		// 如果右子節(jié)點(diǎn)的值,比當(dāng)前最小的值小,就把最小值的位置換成左子節(jié)點(diǎn)的位置
		if (right < heapSieze && array[right] < array[maxValue]) {
			maxValue = right;
		}
		// 如果不相等說明這個(gè)子節(jié)點(diǎn)的值有比自己小的,位置發(fā)生了交換了位置
		if (maxValue != index) {
			swap(array, index, maxValue); // 就要交換位置元素
			// 交換完位置后還需要判斷子節(jié)點(diǎn)是否打破了最小堆的性質(zhì)。最小性質(zhì):兩個(gè)子節(jié)點(diǎn)都比父節(jié)點(diǎn)大。
			buildMinHeap(array, heapSieze, maxValue);
		}
	}
	// 數(shù)組元素交換
	public static void swap(int[] a, int i, int j) {
		int temp = a[i];
		a[i] = a[j];
		a[j] = temp;
	}
}

大頂堆優(yōu)化實(shí)現(xiàn)算法:

import java.util.Arrays;
public class MaxHeapSort {
    private int[] arr;
    public MaxHeapSort(int[] arr){
        this.arr = arr;
    }
    /**
     * 堆排序的主要入口方法,共兩步。
     */
    public void sort(){
        /*
         *  第一步:將數(shù)組堆化
         *  beginIndex = 第一個(gè)非葉子節(jié)點(diǎn)。
         *  從第一個(gè)非葉子節(jié)點(diǎn)開始即可。無需從最后一個(gè)葉子節(jié)點(diǎn)開始。
         *  葉子節(jié)點(diǎn)可以看作已符合堆要求的節(jié)點(diǎn),根節(jié)點(diǎn)就是它自己且自己以下值為最大。
         */
        int len = arr.length - 1;
        int beginIndex = (len - 1) >> 1; 
        for(int i = beginIndex; i >= 0; i--){
            maxHeapify(i, len);
        }
        /*
         * 第二步:對(duì)堆化數(shù)據(jù)排序
         * 每次都是移出最頂層的根節(jié)點(diǎn)A[0],與最尾部節(jié)點(diǎn)位置調(diào)換,同時(shí)遍歷長(zhǎng)度 - 1。
         * 然后從新整理被換到根節(jié)點(diǎn)的末尾元素,使其符合堆的特性。
         * 直至未排序的堆長(zhǎng)度為 0。
         */
        for(int i = len; i > 0; i--){
            swap(0, i);
            maxHeapify(0, i - 1);
        }
    }
    private void swap(int i,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    /**
     * 調(diào)整索引為 index 處的數(shù)據(jù),使其符合堆的特性。
     * @param index 需要堆化處理的數(shù)據(jù)的索引
     * @param len 未排序的堆(數(shù)組)的長(zhǎng)度
     */
    private void maxHeapify(int index,int len){
        int li = (index << 1) + 1; // 左子節(jié)點(diǎn)索引
        int ri = li + 1;           // 右子節(jié)點(diǎn)索引
        int cMax = li;             // 子節(jié)點(diǎn)值最大索引,默認(rèn)左子節(jié)點(diǎn)。
        if(li > len) return;       // 左子節(jié)點(diǎn)索引超出計(jì)算范圍,直接返回。
        if(ri <= len && arr[ri] > arr[li]) // 先判斷左右子節(jié)點(diǎn),哪個(gè)較大。
            cMax = ri;
        if(arr[cMax] > arr[index]){
            swap(cMax, index);      // 如果父節(jié)點(diǎn)被子節(jié)點(diǎn)調(diào)換,
            maxHeapify(cMax, len);  // 則需要繼續(xù)判斷換下后的父節(jié)點(diǎn)是否符合堆的特性。
        }
    }
    /**
     * 測(cè)試用例
     * 輸出:
     * [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9]
     */
    public static void main(String[] args) {
        int[] arr = new int[]{3,5,3,0,8,6,1,5,8,6,2,4,9,4,7,0,1,8,9,7,3,1,2,5,9,7,4,0,2,6};        
        new MaxHeapSort(arr).sort();        
        System.out.println(Arrays.toString(arr));
    }
}

總結(jié)

本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

  • Netty分布式pipeline管道傳播事件的邏輯總結(jié)分析

    Netty分布式pipeline管道傳播事件的邏輯總結(jié)分析

    這篇文章主要為大家介紹了Netty分布式pipeline管道傳播事件總結(jié)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03
  • 基于list stream: reduce的使用實(shí)例

    基于list stream: reduce的使用實(shí)例

    這篇文章主要介紹了list stream: reduce的使用實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 一文徹底搞定Java中常用集合的排序方法

    一文徹底搞定Java中常用集合的排序方法

    在某些特殊的場(chǎng)景下我們需要在Java程序中對(duì)List集合進(jìn)行排序操作,下面這篇文章主要給大家介紹了關(guān)于Java中常用集合的排序方法的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • JDK輸入命令Javac報(bào)錯(cuò)的解決方法

    JDK輸入命令Javac報(bào)錯(cuò)的解決方法

    相信很多人都經(jīng)歷過配置環(huán)境變量失敗的經(jīng)歷,尤其是很多時(shí)候明明按照老師教的步驟或者教程上的方法循規(guī)守矩配置卻還是出錯(cuò),下面我們來解決一個(gè)非常蹊蹺的問題---輸入Java和Java -version都沒問題,但是輸入Javac報(bào)錯(cuò),感興趣的朋友一起看看吧
    2023-11-11
  • 如何用java程序(JSch)運(yùn)行遠(yuǎn)程linux主機(jī)上的shell腳本

    如何用java程序(JSch)運(yùn)行遠(yuǎn)程linux主機(jī)上的shell腳本

    這篇文章主要介紹了如何用java程序(JSch)運(yùn)行遠(yuǎn)程linux主機(jī)上的shell腳本,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-08-08
  • java使用iterator遍歷指定目錄示例分享

    java使用iterator遍歷指定目錄示例分享

    這篇文章主要介紹了java使用iterator遍歷指定目錄示例,需要的朋友可以參考下
    2014-04-04
  • 聊聊注解@controller@service@component@repository的區(qū)別

    聊聊注解@controller@service@component@repository的區(qū)別

    這篇文章主要介紹了聊聊注解@controller@service@component@repository的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java正則表達(dá)式API邊界匹配

    Java正則表達(dá)式API邊界匹配

    這篇文章主要介紹了Java正則表達(dá)式API邊界匹配,文章圍繞主題展開相應(yīng)的相關(guān)資料,具有一定的參考價(jià)值,需要的朋友可以參考一下
    2022-06-06
  • Spring Boot Filter 過濾器的使用方式

    Spring Boot Filter 過濾器的使用方式

    這篇文章主要介紹了Spring Boot Filter 過濾器的使用方式,文章通過圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09
  • 詳解Java反射各種應(yīng)用

    詳解Java反射各種應(yīng)用

    Java除了給我們提供在編譯期得到類的各種信息之外,還通過反射讓我們可以在運(yùn)行期間得到類的各種信息。通過反射獲取類的信息,得到類的信息之后,就可以獲取很多相關(guān)內(nèi)容。下面跟著小編一起來看下吧
    2017-01-01

最新評(píng)論