国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

這篇具有很好參考價值的文章主要介紹了數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

目錄

前言

一.堆的介紹

1.堆的本質(zhì)

2.堆的分類

二.堆的實現(xiàn)(以小根堆為例)

1.關(guān)于二叉樹的兩組重要結(jié)論:

2.堆的物理存儲結(jié)構(gòu)框架(動態(tài)數(shù)組的簡單構(gòu)建)

3. 堆元素插入接口(以小根堆為例)

堆尾元素向上調(diào)整的算法接口:

4.堆元素插入接口測試

5.堆元素插入接口建堆的時間復(fù)雜度分析(建堆時間復(fù)雜度)

6.堆元素刪除接口(同樣以小根堆為例子)

堆元素向下調(diào)整算法接口實現(xiàn):

7.堆元素刪除接口測試

8.逐個刪除堆頂數(shù)據(jù)過程的時間復(fù)雜度分析(刪堆的時間復(fù)雜度分析)

9.堆的實現(xiàn)代碼總覽(以小根堆為例)

10.結(jié)語

?

?


前言

關(guān)于數(shù)據(jù)結(jié)構(gòu)的文章:寫博客的時候我感覺在表達層面上有點費勁,如果文章整體在表述上讓人感覺不夠清楚,懇請讀者原諒,本菜狗已經(jīng)盡力了.

一.堆的介紹

1.堆的本質(zhì)

關(guān)于二叉樹的圖論基礎(chǔ)參見:http://t.csdn.cn/Nv325http://t.csdn.cn/Nv325青菜友情提示:基礎(chǔ)概念對于理解堆的實現(xiàn)與運用非常重要

堆的本質(zhì)映射內(nèi)存中順序存儲結(jié)構(gòu)(數(shù)組)上的完全二叉樹(完全二叉樹是堆的邏輯結(jié)構(gòu)).

堆的圖解:

數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

  • 結(jié)點中的數(shù)據(jù)存儲在相應(yīng)下標的數(shù)組元素空間中

2.堆的分類

堆的兩個類型:

  1. 大根堆:在大根堆中任何子結(jié)構(gòu)的父結(jié)點中存儲的值大于左右孩子結(jié)點中存儲的值(即任何一條連通路徑上從上往下滿足降序排列)數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析
  2. 小根堆:小根堆中任何子結(jié)構(gòu)的父結(jié)點中存儲的值小于左右孩子結(jié)點中存儲的值(即任何一條連通路徑上從上往下滿足升序排列)數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

二.堆的實現(xiàn)(以小根堆為例)

1.關(guān)于二叉樹的兩組重要結(jié)論:

  • 堆的任何一個父結(jié)點的編號parent與其左孩子結(jié)點的編號leftchild滿足如下關(guān)系式:數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析
  • 堆的任何一個父結(jié)點的編號parent與其右孩子結(jié)點的編號rightchild滿足如下關(guān)系式:數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

這兩組重要結(jié)論的推導(dǎo)和分析參見青菜的博客:http://t.csdn.cn/Nv325http://t.csdn.cn/Nv325

2.堆的物理存儲結(jié)構(gòu)框架(動態(tài)數(shù)組的簡單構(gòu)建)

堆是存儲在數(shù)組上的(大小根)完全二叉樹,因此在實現(xiàn)堆之前我們先構(gòu)建一個簡單的動態(tài)數(shù)組作為物理存儲結(jié)構(gòu)框架:

  • 維護動態(tài)數(shù)組的結(jié)構(gòu)體:
    typedef int HPDataType;   //堆元素類型
    typedef struct Heap
    {
    	HPDataType* arry;     //堆區(qū)內(nèi)存指針
    	size_t size;          //堆元素個數(shù)
    	size_t capacity;      //數(shù)組的空間容量
    }HP;
    
  • 堆的基礎(chǔ)操作接口聲明:
    //維護堆的結(jié)構(gòu)體的初始化接口
    void HeapInit(HP* php);
    
    
    //銷毀堆的接口
    void HeapDestroy(HP* php);
    
    
    //堆元素的打印接口
    void HeapPrint(HP* php);
    
    
    //判斷堆是否為空(堆中有無元素)的接口
    bool HeapEmpty(HP* php);
    
    
    //返回堆中元素個數(shù)的接口
    size_t HeapSize(HP* php);
    
    
    //返回堆頂元素(即編號為0的元素)的接口
    HPDataType HeapTop(HP* php);
  • 基礎(chǔ)接口的實現(xiàn):
    //堆結(jié)構(gòu)體的初始化接口
    void HeapInit(HP* ptrheap)
    {
    	assert(ptrheap);
    	ptrheap->arry = NULL;
    	ptrheap->capacity = 0;
    	ptrheap->size = 0;
    
    }
    
    
    //銷毀堆的接口
    void HeapDestroy(HP* ptrheap)
    {
    	assert(ptrheap);
    	free(ptrheap->arry);
    	ptrheap->arry = NULL;
    	ptrheap->capacity = 0;
    	ptrheap->size = 0;
    }
    
    
    //堆的打印接口
    void HeapPrint(HP* ptrheap)
    {
    	assert(ptrheap);
    	size_t tem = 0;
    	for (tem = 0; tem < ptrheap->size; ++tem)
    	{
    		printf("%d->", ptrheap->arry[tem]);
    
    	}
    	printf("END\n");
    }
    
    
    
    //判斷堆是否為空的接口
    bool HeapEmpty(HP* ptrheap)
    {
    	assert(ptrheap);
    	return (0 == ptrheap->size);
    }
    
    
    //返回堆元素個數(shù)的接口
    size_t HeapSize(HP* ptrheap)
    {
    	assert(ptrheap);
    	return ptrheap->size;
    }
    
    
    //返回堆頂元素的接口
    HPDataType HeapTop(HP* ptrheap)
    {
    	assert(!HeapEmpty(ptrheap));
    	return ptrheap->arry[0];
    }

    主函數(shù)建堆時的內(nèi)存布局簡單圖示:

    數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

    堆區(qū)上的數(shù)組是在堆元素插入接口中調(diào)用malloc申請出來的(我們暫時還沒有實現(xiàn)堆元素插入的接口)

  • 以上是堆在內(nèi)存中的存儲框架的構(gòu)建,然而堆的核心接口是堆元素的插入和刪除接口

3. 堆元素插入接口(以小根堆為例)

  • 堆的構(gòu)建過程概述:向堆尾逐個插入元素(將元素尾插進數(shù)組中),每次插入一個元素后都通過堆的調(diào)整算法逐層向上(二叉樹意義上的層)(子結(jié)點和父結(jié)點進行大小比較,若不滿足小根堆的性質(zhì)就進行數(shù)值交換)調(diào)整該元素在堆中的位置以保持小根堆的數(shù)據(jù)結(jié)構(gòu).
  • 算法過程簡圖:數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析
  • 從0個結(jié)點的堆開始,每尾插一個元素我們都按照小根堆的性質(zhì)(通過元素向上調(diào)整算法)調(diào)整該元素在堆中的位置來保持小根堆的數(shù)據(jù)結(jié)構(gòu),我們就是以這樣的基本思想來建堆的.
  • 建堆的思路細解:數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析
  • 顯然在堆尾插入元素6后破壞了小根堆的結(jié)構(gòu)(6小于其父結(jié)點20不滿足小根堆的性質(zhì))(任意一個子結(jié)構(gòu)中子結(jié)點與父結(jié)點進行大小比較就可以知道堆的結(jié)構(gòu)有沒有被破壞),因此我們就需要將6這個元素逐層向二叉樹的上層調(diào)整(每個調(diào)整的子步驟都是從子結(jié)點向父結(jié)點調(diào)整)
  • 通過樹的子結(jié)構(gòu)中子結(jié)點與父結(jié)點的編號關(guān)系可以找到堆尾8號結(jié)點(元素6)的父結(jié)點(元素20,結(jié)點編號為3)
  • 接下來將元素逐層向上調(diào)整(子結(jié)點和父結(jié)點進行數(shù)值交換)恢復(fù)小根堆的數(shù)據(jù)結(jié)構(gòu):

向上調(diào)整的第一步:將尾插進堆尾的結(jié)點(8號結(jié)點,值為6)與其父結(jié)點(3號結(jié)點,值為20)進行比較,父結(jié)點大于子結(jié)點,交換父結(jié)點與子結(jié)點的值(下圖中的Swap是元素數(shù)值交換接口):數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

向上調(diào)整的第二步:交換了8號結(jié)點和3號結(jié)點的值后,我們發(fā)現(xiàn)小堆結(jié)構(gòu)依然不成立,于是重復(fù)上述的調(diào)整過程,將元素6繼續(xù)向上層的父結(jié)點位置進行調(diào)整,不斷重復(fù)迭代直到小堆結(jié)構(gòu)恢復(fù)或者元素6被調(diào)到堆頂為止:數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

  • 可見經(jīng)過再一次調(diào)整后,元素6來到了1號結(jié)點,此時1號結(jié)點大于0號結(jié)點,小堆數(shù)據(jù)結(jié)構(gòu)得到恢復(fù),調(diào)整過程終止。

同時不難分析出,任何堆尾元素向上調(diào)整的過程都是在堆尾到堆頂元素的連通路徑上進行的(該路徑長度數(shù)量級為logk)(k為當前樹結(jié)點總個數(shù))):數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

  • 根據(jù)上面的分析我們可以設(shè)計出一個堆尾元素向上調(diào)整的算法接口:

接口首部: arry是指向數(shù)組首地址的指針,child是堆尾元素的編號(結(jié)點編號同時也是該元素的數(shù)組下標)

void AdjustUp(HPDataType* arry, size_t child);

堆尾元素向上調(diào)整的算法接口:

//元素交換接口
void Swap(HPDataType* e1, HPDataType* e2)
{
	assert(e1 && e2);
	HPDataType tem = *e1;
	*e1 = *e2;
	*e2 = tem;
}



//小堆元素的向上調(diào)整接口
void AdjustUp(HPDataType* arry, size_t child)  //child表示孩子結(jié)點的編號
{
	assert(arry);
	size_t parent = (child - 1) / 2;    //算出父結(jié)點的數(shù)組下標(堆編號)
	while (child > 0)			        //child減小到0時則調(diào)整結(jié)束(堆尾元素已調(diào)到堆頂)
	{
		if (arry[child] < arry[parent]) //父結(jié)點大于子結(jié)點,則子結(jié)點需要上調(diào)以保持小堆的結(jié)構(gòu)
		{
			Swap(arry + child, arry+parent);
			child = parent;				//將原父結(jié)點作為新的子結(jié)點繼續(xù)迭代過程
			parent = (child - 1) / 2;	//算出父結(jié)點的數(shù)組下標(堆編號)
		}
		else
		{
			break;						//父結(jié)點不大于子結(jié)點,則堆結(jié)構(gòu)恢復(fù),無需再調(diào)整
		}
	}
}

堆尾元素向上調(diào)整的算法接口中需要注意的細節(jié):

  • 調(diào)整循環(huán)的終止條件有兩個:
  1. 一個是被調(diào)整元素被調(diào)到了堆頂(即child=0的時候)
  2. 被調(diào)整元素大于其父結(jié)點時小堆的數(shù)據(jù)結(jié)構(gòu)得到恢復(fù),break終止循環(huán)

有了該接口,我們就可以設(shè)計堆元素插入接口:

  • HP * ptrheap是指向堆結(jié)構(gòu)體的結(jié)構(gòu)體指針,x是要插入的元素的數(shù)值
void HeapPush(HP* ptrheap, HPDataType x)
// 插入一個小堆元素的接口
// 插入x以后,保持其數(shù)據(jù)結(jié)構(gòu)依舊是小堆
void HeapPush(HP* ptrheap, HPDataType x)        //ptrheap是指向堆結(jié)構(gòu)體的結(jié)構(gòu)體指針
{
	assert(ptrheap);
	if (ptrheap->capacity == ptrheap->size)	    //數(shù)組容量檢查,容量不夠則擴容
	{
		size_t newcapacity = (0 == ptrheap->capacity) ? 4 : 2 * ptrheap->capacity;
        //注意容量為0則將堆區(qū)數(shù)組容量調(diào)整為4
		HPDataType* tem = (HPDataType*)realloc(ptrheap->arry, newcapacity*sizeof(HPDataType)); //空間不夠則擴容
		assert(tem);             //檢查擴容是否成功
		ptrheap->arry = tem;     //將堆區(qū)地址賦給結(jié)構(gòu)體中的指針成員
		ptrheap->capacity = newcapacity; //容量標記更新
	}
	ptrheap->arry[ptrheap->size] = x;			//元素尾插入堆
	ptrheap->size++;


	//將尾插的元素進行向上調(diào)整以保持堆的數(shù)據(jù)結(jié)構(gòu)(復(fù)用元素向上調(diào)整接口)
	AdjustUp(ptrheap->arry, ptrheap->size - 1); //根據(jù)完全二叉樹的結(jié)構(gòu)特點可知堆尾元素                
                                                //在二叉樹中編號為size-1
}
  • 該接口可以完成一個堆元素的插入(而且每次完成插入后小根堆的數(shù)據(jù)結(jié)構(gòu)都可以得到保持)
  • 如果我們想要創(chuàng)建一個n個元素小根堆,則n次調(diào)用HeapPush接口即可

4.堆元素插入接口測試

現(xiàn)在我們試著用HeapPush接口來構(gòu)建六個元素的小堆,并將堆打印出來觀察其邏輯結(jié)構(gòu):

  • 主函數(shù)代碼:
    int main ()
    {
        HP hp;              //創(chuàng)建一個堆結(jié)構(gòu)體
    	HeapInit(&hp);      //結(jié)構(gòu)體初始化
    	HeapPush(&hp, 1);   //調(diào)用堆元素插入接口建堆
    	HeapPush(&hp, 5);
    	HeapPush(&hp, 0);
    	HeapPush(&hp, 8);
    	HeapPush(&hp, 3);
    	HeapPush(&hp, 9);
    	HeapPrint(&hp);     //打印堆元素
    }

    數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

    我們來觀察一下打印出來的小根堆的邏輯結(jié)構(gòu):數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

5.堆元素插入接口建堆的時間復(fù)雜度分析(建堆時間復(fù)雜度)

  • 假設(shè)現(xiàn)在我們要調(diào)用HeapPush接口創(chuàng)建一個N個元素的小堆(調(diào)用N次HeapPush接口)
  • 注意:我們只關(guān)注時間復(fù)雜度最壞的情況(即假設(shè)每個插入堆尾的元素都沿著連通路徑向上調(diào)整到了堆頂)數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析
  • 分析建堆的時間復(fù)雜度時我們將堆看成滿二叉樹(滿二叉樹是特殊的完全二叉樹),也就是說當堆有N個元素時,堆的層數(shù)h=log(N+1)(該對數(shù)以2為底,計算機科學(xué)中我們一般不關(guān)注對數(shù)的底數(shù))數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析
  • 接下來我們將所有元素的向上調(diào)整次數(shù)進行求和,即可得出用HeapPush接口建堆的時間復(fù)雜度:數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析
  • 錯位相減法可對上面公式進行求和計算:數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析
  • 因此可以得到用堆元素插入接口(堆元素向上調(diào)整算法)建堆(建立N個元素的小根堆)的時間復(fù)雜度為:O(NlogN);

6.堆元素刪除接口(同樣以小根堆為例子)

我們討論的堆元素刪除指的是:刪除堆頂數(shù)據(jù)

刪除堆頂?shù)脑?/span>的基本思路:

  • 將堆頂元素與堆尾元素進行交換,然后令維護堆的結(jié)構(gòu)體中的size(記錄堆元素個數(shù)的成員變量)減一(相當于移除堆尾元素)數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析
  • 然而原來的堆尾元素被交換到堆頂位置后,小根堆的數(shù)據(jù)結(jié)構(gòu)會被破壞,因此我們需要將堆頂元素進行向下調(diào)整操作:數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析
  • 堆頂元素向下調(diào)整圖解演示:數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析
  • 同樣不難分析出,任何堆頂元素向下調(diào)整的過程都是在堆頂?shù)蕉盐苍氐?/strong>連通路徑上進行的(該路徑長度數(shù)量級為logk)(k為當前樹結(jié)點總個數(shù))):數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析
  • 通過算法分析我們可以設(shè)計一個堆元素向下調(diào)整算法接口:??

arry是指向內(nèi)存堆區(qū)數(shù)組首地址的指針,size是堆的結(jié)點總個數(shù),parent是待調(diào)整結(jié)點在堆中的編號;

void AdjustDown(HPDataType* arry,size_t size,size_t parent)

堆元素向下調(diào)整算法接口實現(xiàn):

//元素交換接口
void Swap(HPDataType* e1, HPDataType* e2)
{
	assert(e1 && e2);
	HPDataType tem = *e1;
	*e1 = *e2;
	*e2 = tem;
}

//小堆元素的向下調(diào)整接口
void AdjustDown(HPDataType* arry,size_t size,size_t parent)
{
	assert(arry);
	size_t child = 2 * parent + 1;   //確定父結(jié)點的左孩子的編號
	while (child < size)			 //child增加到大于或等于size時說明被調(diào)整元素被調(diào)整到了葉結(jié)點上,調(diào)整過程結(jié)束
	{
		if (child + 1 < size && arry[child + 1] < arry[child])//確定左右孩子中較小的孩子結(jié)點
		{
			++child;   //條件成立說明右孩子較小,child更新為右孩子編號
		}
		if ( arry[child] < arry[parent])//父結(jié)點大于子結(jié)點,則父結(jié)點需要下調(diào)以保持小堆的結(jié)構(gòu)
		{
			Swap(arry + parent, arry + child);//父子結(jié)點進行值交換
			parent = child;				      //將原來的子結(jié)點作為新的父結(jié)點繼續(xù)迭代過程
			child = 2 * parent + 1;		      //繼續(xù)向下找另外一個子結(jié)點
		}
		else
		{
			break;						//父結(jié)點不大于子結(jié)點,則小堆結(jié)構(gòu)成立,無需繼續(xù)調(diào)整
		}
	}
}
  • ?注意一下算法接口的一些細節(jié)和邊界條件:
  1. child<size說明被調(diào)整元素已經(jīng)被調(diào)整到葉結(jié)點位置,結(jié)束調(diào)整過程
  2. 算法接口中我們只設(shè)計了一個child變量記錄當前父結(jié)點的孩子結(jié)點的編號,接著通過arry[child + 1] < arry[child]來比較左右孩子大小來確定child到底是取左孩子的編號還是取右孩子的編號(注意child+1<size判斷語句是為了確定右孩子是否存在)
  • 有了堆元素向下調(diào)整算法接口我們就可以實現(xiàn)堆頂數(shù)據(jù)刪除接口:
    //刪除堆頂元素
    void HeapPop(HP* ptrheap)
    {
    	assert(ptrheap);
    	Swap(ptrheap->arry, ptrheap->arry + ptrheap->size - 1); //交換堆頂與堆尾元素
    	ptrheap->size--;										//刪除堆尾元素
    	AdjustDown(ptrheap->arry, ptrheap->size, 0);    //將堆頂元素進行向下調(diào)整以保持堆的數(shù)據(jù)結(jié)構(gòu)
    }

    注意:AdjustDown接口的傳參中,ptrheadp->size指的是堆元素的總個數(shù),由于我們要將堆頂元素向下調(diào)整,因此傳入的被調(diào)整結(jié)點編號為0

  • 該接口每調(diào)用一次就可以刪除一個堆頂數(shù)據(jù)(并保持小根堆的數(shù)據(jù)結(jié)構(gòu)):數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

7.堆元素刪除接口測試

我們先6次調(diào)用HeapPush接口創(chuàng)建一個六個元素的小堆,然后再6次調(diào)用HeapPop接口逐個刪除堆頂元素(每刪除一個堆頂數(shù)據(jù)打印一次堆,借此觀察每次刪除堆頂數(shù)據(jù)后堆的數(shù)據(jù)結(jié)構(gòu)).

int main ()
{
    HP hp;
	HeapInit(&hp);
	HeapPush(&hp, 1);
	HeapPush(&hp, 5);
	HeapPush(&hp, 0);
	HeapPush(&hp, 8);
	HeapPush(&hp, 3);
	HeapPush(&hp, 9);
	HeapPrint(&hp);

	HeapPop(&hp);
	HeapPrint(&hp);
	HeapPop(&hp);
	HeapPrint(&hp);
	HeapPop(&hp);
	HeapPrint(&hp);
	HeapPop(&hp);
	HeapPrint(&hp);
	HeapPop(&hp);
	HeapPrint(&hp);


	HeapDestroy(&hp);

}

數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

觀察小根堆的數(shù)據(jù)結(jié)構(gòu)(邏輯結(jié)構(gòu)):?

數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

  • 可見逐個刪除堆頂數(shù)據(jù)的過程中小根堆的數(shù)據(jù)結(jié)構(gòu)得到了保持

8.逐個刪除堆頂數(shù)據(jù)過程的時間復(fù)雜度分析(刪堆的時間復(fù)雜度分析)

假設(shè)現(xiàn)在有一個N個元素的小根堆,我們逐個刪除堆頂數(shù)據(jù)直到將堆刪空。

現(xiàn)在我們來分析這個過程的時間復(fù)雜度(我們同樣將二叉樹看成滿二叉樹來分析):

  • 同樣我們考慮的是時間復(fù)雜度最壞的情況:即每次調(diào)用堆元素刪除接口后,被交換到堆頂?shù)脑囟?/span>沿著連通路徑向下被調(diào)整到葉結(jié)點
  • 分析:假設(shè)堆的高度為h,則滿樹的總結(jié)點個數(shù)N為? : 2^h?- 1?
    需要注意的是滿樹的第h層(最后一層)的結(jié)點個數(shù)為: 2^(h-1)
    數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

所以滿樹的最后一層的結(jié)點個數(shù)占樹的總結(jié)點個數(shù)的一半

因此如果我們刪去滿樹的前h-1層的所有結(jié)點,則由最后一層結(jié)點所構(gòu)成的新樹的高度大致為h-1:數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

這也就意味著,在刪除前h-1層結(jié)點的過程中樹的總高度幾乎不會發(fā)生變化:

因此可以得到刪除滿樹的前h-1層所有結(jié)點過程中,每個被交換到堆頂?shù)脑囟家幌蛳抡{(diào)整log(N+1)次,則刪除滿樹的前h-1層所有結(jié)點過程中,向下調(diào)整算法中循環(huán)語句執(zhí)行總次數(shù)約為:

數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

接著,對于由原樹的最后一層(第h層)結(jié)點構(gòu)成的新樹(層數(shù)為h-1層)我們可以作類似的分析(即先刪除其前h-2層結(jié)點,得到由其第h-1層結(jié)點構(gòu)成的新樹),以這種方式遞推下去直到樹被刪空為止:數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

  • 由此可得,?逐個刪除堆頂數(shù)據(jù)直到將堆刪空過程中,堆元素的向下調(diào)整算法中循環(huán)語句總的執(zhí)行次數(shù)估算為:

數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析

該多項式的和化為大O階漸進表示法結(jié)果為O(NlogN).?

  • 逐個刪除堆頂數(shù)據(jù)直到將堆刪空的過程的時間復(fù)雜度分析為:O(NlogN)

9.堆的實現(xiàn)代碼總覽(以小根堆為例)

接口和結(jié)構(gòu)類型聲明的頭文件:

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>

//實現(xiàn)小堆

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* arry;
	size_t size;
	size_t capacity;
}HP;


//堆的初始化接口
void HeapInit(HP* php);
//銷毀堆的接口
void HeapDestroy(HP* php);
//堆的打印接口
void HeapPrint(HP* php);
//判斷堆是否為空的接口
bool HeapEmpty(HP* php);
//返回堆元素個數(shù)的接口
size_t HeapSize(HP* php);
//返回堆頂元素的接口
HPDataType HeapTop(HP* php);


void Swap(HPDataType* pa, HPDataType* pb);
//堆元素向上調(diào)整算法接口
void AdjustUp(HPDataType* a, size_t child);
//堆元素向下調(diào)整算法接口
void AdjustDown(HPDataType* a, size_t size,size_t parent);
// 插入元素x以后,保持數(shù)據(jù)結(jié)構(gòu)是(大/小)堆
void HeapPush(HP* php, HPDataType x);
// 刪除堆頂?shù)臄?shù)據(jù)后,保持數(shù)據(jù)結(jié)構(gòu)是(大/小)堆
void HeapPop(HP* php);

各個堆操作接口的實現(xiàn):

#include "heap.h"


//堆的初始化接口
void HeapInit(HP* ptrheap)
{
	assert(ptrheap);
	ptrheap->arry = NULL;
	ptrheap->capacity = 0;
	ptrheap->size = 0;

}
//銷毀堆的接口
void HeapDestroy(HP* ptrheap)
{
	assert(ptrheap);
	free(ptrheap->arry);
	ptrheap->arry = NULL;
	ptrheap->capacity = 0;
	ptrheap->size = 0;
}


//堆的打印接口
void HeapPrint(HP* ptrheap)
{
	assert(ptrheap);
	size_t tem = 0;
	for (tem = 0; tem < ptrheap->size; ++tem)
	{
		printf("%d->", ptrheap->arry[tem]);

	}
	printf("END\n");
}



//判斷堆是否為空的接口
bool HeapEmpty(HP* ptrheap)
{
	assert(ptrheap);
	return (0 == ptrheap->size);
}


//返回堆元素個數(shù)的接口
size_t HeapSize(HP* ptrheap)
{
	assert(ptrheap);
	return ptrheap->size;
}




//返回堆頂元素的接口
HPDataType HeapTop(HP* ptrheap)
{
	assert(!HeapEmpty(ptrheap));
	return ptrheap->arry[0];
}





//元素交換接口
void Swap(HPDataType* e1, HPDataType* e2)
{
	assert(e1 && e2);
	HPDataType tem = *e1;
	*e1 = *e2;
	*e2 = tem;
}



//小堆元素的向上調(diào)整接口
void AdjustUp(HPDataType* arry, size_t child)  //child表示孩子結(jié)點的編號
{
	assert(arry);
	size_t parent = (child - 1) / 2;
	while (child > 0)						   //child減小到0時則調(diào)整結(jié)束
	{
		if (arry[child] < arry[parent])        //父結(jié)點大于子結(jié)點,則子結(jié)點需要上調(diào)以保持小堆的結(jié)構(gòu)
		{
			Swap(arry + child, arry+parent);
			child = parent;				//將原父結(jié)點作為新的子結(jié)點繼續(xù)迭代過程
			parent = (child - 1) / 2;	//繼續(xù)向上找另外一個父結(jié)點
		}
		else
		{
			break;						//父結(jié)點不大于子結(jié)點,則堆結(jié)構(gòu)任然成立,無需調(diào)整
		}
	}
}

// 插入一個小堆元素的接口
// 插入x以后,保持其數(shù)據(jù)結(jié)構(gòu)依舊是小堆
void HeapPush(HP* ptrheap, HPDataType x)
{
	assert(ptrheap);
	if (ptrheap->capacity == ptrheap->size)	//容量檢查,容量不夠則擴容
	{
		size_t newcapacity = (0 == ptrheap->capacity) ? 4 : 2 * ptrheap->capacity;
		HPDataType* tem = (HPDataType*)realloc(ptrheap->arry, newcapacity * sizeof(HPDataType));
		assert(tem);
		ptrheap->arry = tem;
		ptrheap->capacity = newcapacity;
	}
	ptrheap->arry[ptrheap->size] = x;			//先尾插一個元素
	ptrheap->size++;
	//將尾插的元素進行向上調(diào)整以保持堆的數(shù)據(jù)結(jié)構(gòu)
	AdjustUp(ptrheap->arry, ptrheap->size - 1); //根據(jù)完全二叉樹的結(jié)構(gòu)特點可知尾插進數(shù)組的元素在二叉樹中編號為size-1
}



//小堆元素的向下調(diào)整接口
void AdjustDown(HPDataType* arry,size_t size,size_t parent)
{
	assert(arry);
	size_t child = 2 * parent + 1;   //確定父結(jié)點的左孩子的編號
	while (child < size)			 //child增加到大于或等于size時則調(diào)整結(jié)束
	{
		if (child + 1 < size && arry[child + 1] < arry[child]) //確定左右孩子中較小的孩子結(jié)點
		{
			++child;
		}
		if ( arry[child] < arry[parent])//父結(jié)點大于子結(jié)點,則子結(jié)點需要上調(diào)以保持小堆的結(jié)構(gòu)
		{
			Swap(arry + parent, arry + child);
			parent = child;				//將原子結(jié)點作為新的父結(jié)點繼續(xù)迭代過程
			child = 2 * parent + 1;		//繼續(xù)向下找另外一個子結(jié)點
		}
		else
		{
			break;						//父結(jié)點不大于子結(jié)點,則堆結(jié)構(gòu)任然成立,無需調(diào)整
		}
	}
}


//刪除堆頂元素
void HeapPop(HP* ptrheap)
{
	assert(ptrheap);
	Swap(ptrheap->arry, ptrheap->arry + ptrheap->size - 1); //交換堆頂與堆尾元素
	ptrheap->size--;										//刪除堆尾元素
	AdjustDown(ptrheap->arry, ptrheap->size, 0);			//將堆頂元素進行向下調(diào)整以保持堆的數(shù)據(jù)結(jié)構(gòu)
}

我們只需將小根堆的堆元素上下調(diào)整算法接口中的子父結(jié)點值大小比較符號改為大于號即可實現(xiàn)大根堆

10.結(jié)語

  • 本文的核心:通過堆元素的上下調(diào)整算法接口完成堆的構(gòu)建和刪空(并分析過程的時間復(fù)雜度)
  • 堆元素上下調(diào)整算法接口在后續(xù)的堆排序和TopK問題中還會直接用到
  • 堆的高效性來源于如下數(shù)學(xué)思想:利用指數(shù)級展開的數(shù)據(jù)結(jié)構(gòu)模型實現(xiàn)對數(shù)級連通路徑遍歷算法,從而降低了排序和查找的時間復(fù)雜度(不得不說前人的創(chuàng)造真的是無可比擬)

數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析文章來源地址http://www.zghlxwxcb.cn/news/detail-418061.html

到了這里,關(guān)于數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)與建堆時間復(fù)雜度分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)

    數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)

    如果有一個關(guān)鍵碼的集合 K = { k1 ,k2 ,k3 ,…,kn },把它的所有元素按完全二叉樹的順序存儲方式存儲在一個一維數(shù)組中,并且 k(i)? k(i*2+1) 和?k(i)? k(i*2+2), i = 0 , 1 , 2…,則稱為小堆 ( 或大堆 ) 。將根節(jié)點最大的堆叫做最大堆或大根堆,根節(jié)點最小的堆叫做最小堆或小

    2024年02月13日
    瀏覽(21)
  • 數(shù)據(jù)結(jié)構(gòu)---堆的實現(xiàn)

    數(shù)據(jù)結(jié)構(gòu)---堆的實現(xiàn)

    前言 一、什么是堆? 二、 堆的實現(xiàn) 1. 堆的結(jié)構(gòu)? 2. 接口實現(xiàn) 總結(jié) 堆(Heap)是計算機科學(xué)中一類特殊的數(shù)據(jù)結(jié)構(gòu),是最高效的優(yōu)先級隊列。堆通常是一個可以被看做一棵完全二叉樹的數(shù)組對象。 現(xiàn)實中我們通常把 堆(一種二叉樹) 使用 順序結(jié)構(gòu)的數(shù)組 來存儲, 大根堆 :根節(jié)

    2024年02月02日
    瀏覽(20)
  • 數(shù)據(jù)結(jié)構(gòu)之堆的結(jié)構(gòu)與實現(xiàn)

    數(shù)據(jù)結(jié)構(gòu)之堆的結(jié)構(gòu)與實現(xiàn)

    目錄 一、堆的概念及結(jié)構(gòu) 1.1堆的概念 ?1.2堆的性質(zhì) 1.3堆的結(jié)構(gòu) 二、堆的實現(xiàn) 2.1堆向下調(diào)整算法(父親與孩子做比較) ?2.2堆的向上調(diào)整算法(孩子與父親做比較) 2.3堆的創(chuàng)建(向下建堆) ?2.4向下建堆的時間復(fù)雜度 2.5堆的插入 2.6堆的刪除 2.7堆的完整代碼實現(xiàn) 三、堆的應(yīng)

    2024年02月08日
    瀏覽(28)
  • 數(shù)據(jù)結(jié)構(gòu)—小堆的實現(xiàn)

    數(shù)據(jù)結(jié)構(gòu)—小堆的實現(xiàn)

    前言:前面我們已經(jīng)學(xué)習(xí)了二叉樹,今天我們來學(xué)習(xí)堆,堆也是一個二叉樹,堆有大堆有小堆,大堆父節(jié)點大于子節(jié)點,小堆父節(jié)點總小于子節(jié)點,我們在學(xué)習(xí)C語言的時候也有一個堆的概念,那個堆是操作系統(tǒng)中的堆,與我們今天所學(xué)的堆全然不同。我們就來實現(xiàn)下小堆。

    2024年02月05日
    瀏覽(24)
  • 數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)(C實現(xiàn))

    數(shù)據(jù)結(jié)構(gòu):堆的實現(xiàn)(C實現(xiàn))

    個人主頁 : 個人主頁 個人專欄 : 《數(shù)據(jù)結(jié)構(gòu)》 《C語言》 當一顆完全二叉樹用順序表來存儲時,其被稱為堆。 堆總是一棵完全二叉樹 堆的某個節(jié)點的值總是不大于(大堆)或不小于(小堆)其父節(jié)點的值 其可以被用來解決top k 問題 或 堆排序 下面就是要實現(xiàn)的堆的功能 重點在

    2024年02月13日
    瀏覽(25)
  • 【數(shù)據(jù)結(jié)構(gòu)之堆的實現(xiàn)】

    前言: 前篇學(xué)習(xí)了 數(shù)據(jù)結(jié)構(gòu)之樹和二叉樹的基本概念知識,那么這篇繼續(xù)學(xué)習(xí)怎么實現(xiàn)基本的操作。所以先建議看完上篇知識點,才有助于消化知識和理解。 / 知識點匯總 / 概念 :堆(Heap)是計算機科學(xué)中一類特殊的數(shù)據(jù)結(jié)構(gòu),是最高效的優(yōu)先級隊列。堆通常是一個可以被看

    2024年01月19日
    瀏覽(28)
  • 【數(shù)據(jù)結(jié)構(gòu)】堆的實現(xiàn)及應(yīng)用

    【數(shù)據(jù)結(jié)構(gòu)】堆的實現(xiàn)及應(yīng)用

    簡單不先于復(fù)雜,而是在復(fù)雜之后 。 1.1 二叉樹的順序結(jié)構(gòu) 普通的二叉樹是不適合用數(shù)組來存儲的,因為可能會存在大量的空間浪費。 而完全二叉樹更適合使用順序結(jié)構(gòu)存儲。 現(xiàn)實中我們通常把堆(一種二叉樹)使用順序結(jié)構(gòu)的數(shù)組來存儲,需要注意的是這里的堆和操作系

    2024年02月21日
    瀏覽(28)
  • 數(shù)據(jù)結(jié)構(gòu)——二叉樹(堆的實現(xiàn))

    數(shù)據(jù)結(jié)構(gòu)——二叉樹(堆的實現(xiàn))

    目錄 ? 樹概念及結(jié)構(gòu) 樹的相關(guān)概念 樹的表示? 二叉樹的概念及結(jié)構(gòu)?? 堆 堆的實現(xiàn)? ?結(jié)構(gòu)體建立 初始化? ?添加元素 ?打印堆 ?刪除堆首元素 ?返回首元素 ?判斷是否為空 空間銷毀? 刷題找工作的好網(wǎng)站——??途W(wǎng) ??途W(wǎng) - 找工作神器|筆試題庫|面試經(jīng)驗|實習(xí)招聘內(nèi)推,

    2024年02月11日
    瀏覽(24)
  • 【數(shù)據(jù)結(jié)構(gòu)】結(jié)構(gòu)實現(xiàn):順序存儲模式實現(xiàn)堆的相關(guān)操作

    【數(shù)據(jù)結(jié)構(gòu)】結(jié)構(gòu)實現(xiàn):順序存儲模式實現(xiàn)堆的相關(guān)操作

    ?? 紙上得來終覺淺, 絕知此事要躬行。 ??主頁:June-Frost ??專欄:數(shù)據(jù)結(jié)構(gòu) ??該文章著重講解了使用順序結(jié)構(gòu)實現(xiàn)堆的插入和刪除等操作。 ?二叉樹的順序存儲是指將二叉樹中的所有節(jié)點按照一定的順序(一層一層)存儲到一個數(shù)組中。 ?我們可以通過數(shù)組下標來表示

    2024年02月08日
    瀏覽(19)
  • 【數(shù)據(jù)結(jié)構(gòu)】——二叉樹堆的實現(xiàn)

    【數(shù)據(jù)結(jié)構(gòu)】——二叉樹堆的實現(xiàn)

    ?大佬們點點關(guān)注,點點贊?! 在上篇博客中我們已經(jīng)介紹了樹和二叉樹的相關(guān)概念,相信大家都已經(jīng)清楚了樹和二叉樹的基本思想,下面我們就來著重看看二叉樹堆的實現(xiàn)。 在看堆的實現(xiàn),我們先看看二叉樹的順序存儲 二叉樹的順序存儲就是以順序表來實現(xiàn)的,也就是把

    2024年04月13日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包