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

C語(yǔ)言堆實(shí)現(xiàn)建堆算法和堆排序

 更新時(shí)間:2024年09月02日 09:28:09   作者:敲上癮  
本文主要介紹了C語(yǔ)言堆實(shí)現(xiàn)建堆算法和堆排序,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

一.什么是堆?

1.堆 

堆就是完全二叉樹,而且是一種特殊的完全二叉樹,它需要滿足每一個(gè)父節(jié)點(diǎn)都大于子節(jié)點(diǎn),稱為大堆,或每一個(gè)父節(jié)點(diǎn)都小于子節(jié)點(diǎn),稱為小堆。而對(duì)兄弟節(jié)點(diǎn)之間的大小關(guān)系并沒有要求(為此它并不是有序的)。如下:

2.堆的儲(chǔ)存 

對(duì)于完全二叉樹有一個(gè)更好的儲(chǔ)存方法,就是用順序表來(lái)儲(chǔ)存,相比鏈?zhǔn)絻?chǔ)存使用順序表儲(chǔ)存的一個(gè)很大的好處在于知道一個(gè)結(jié)點(diǎn)可以很容易的算出它父結(jié)點(diǎn)和子結(jié)點(diǎn)的下標(biāo),還有可以隨機(jī)訪問(wèn)。

父子結(jié)點(diǎn)下標(biāo)計(jì)算公式 :

  • 左子結(jié)點(diǎn)下標(biāo) = 父結(jié)點(diǎn)下標(biāo)*2+1
  • 右子結(jié)點(diǎn)下標(biāo) = 父結(jié)點(diǎn)下標(biāo)*2+2
  • 父結(jié)點(diǎn)下標(biāo) = (子結(jié)點(diǎn)下標(biāo)-1) / 2 

二.堆結(jié)構(gòu)的創(chuàng)建

1.頭文件的聲明:

Heap.h

#pragma
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define HpDataType int
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* arr;
	int size;
	int cap;
}Heap;
void HeapInit(Heap* php);//堆的初始化
void HeapDestory(Heap* hp);//堆的銷毀
void HeapPush(Heap* hp, HPDataType x);//堆的插入
void HeapPop(Heap* hp);//堆的刪除
HPDataType HeapTop(Heap* hp);//取堆頂?shù)臄?shù)據(jù)
int HeapSize(Heap* hp);//堆的數(shù)據(jù)個(gè)數(shù)
int HeapEmpty(Heap* hp);//堆的判空

void AdjustUP(HpDataType* arr, int child);//向上調(diào)整
void AdjustDOWN(HpDataType* arr, int size, int parent);//向下調(diào)整
void Swap(HpDataType* a, HpDataType* b);//元素的交換

其中堆的初始化,堆的銷毀,堆的數(shù)據(jù)個(gè)數(shù),堆的判空,和取堆頂數(shù)據(jù)和順序表的操作是一樣的這里重點(diǎn)來(lái)學(xué)一下堆的插入,堆的刪除。

2.向上調(diào)整

插入元素呢直接往數(shù)組最后插入就可以,但是插入后就不一定是堆結(jié)構(gòu)的,所以需要調(diào)整。例如一個(gè)大堆:

向大堆中插入53

調(diào)整后:

代碼示例:

void AdjustUP(HpDataType* arr,int child)
{
	int parent = (child - 1) / 2;//計(jì)算父節(jié)點(diǎn)下標(biāo)
	while (child>0)//注意這里不能是parent>0
	{
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);//封裝一個(gè)函數(shù)進(jìn)行交換
			child = parent;//更新子節(jié)點(diǎn)
			parent = (child - 1) / 2;//更新父節(jié)點(diǎn)
		}
		else
			break;
	}
}

★如果是小堆只需要把if條件的大于號(hào)改為小于號(hào)

3.向下調(diào)整 

要注意刪除元素我們刪除的不是尾元素,這樣毫無(wú)意義,我們刪除的是下標(biāo)為0位置的元素,它是整個(gè)堆中最小或最大的元素。怎么刪除呢?直接將它刪除然后后面的元素在覆蓋上嗎?這樣做的話,它就不是堆了,而且元素之間關(guān)系將會(huì)全部混亂,就需要從0開始創(chuàng)建堆,效率非常低,我們可以把首元素與尾元素互換然后刪除尾元素,雖然這個(gè)操作過(guò)后它也可能就不是堆了,不過(guò)我們可以將首元素向下調(diào)整,讓它成為堆。比剛才的方案效率要高得多。

比如我們刪除大堆中的一個(gè)元素

調(diào)整過(guò)程:

調(diào)整后的結(jié)果:

代碼示例:

void AdjustDOWN(HpDataType* arr, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if ((child+1)<size&&arr[child] < arr[child + 1])
			child++;
		if (arr[child] > arr[parent])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}

★如果是小堆只需要把if條件里兄弟節(jié)點(diǎn)的大小關(guān)系和父子節(jié)點(diǎn)的大小關(guān)系改變一下就行

4.源碼: 

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void HeapInit(Heap* ps)//初始化
{
	assert(ps);
	ps->arr = NULL;
	ps->cap = ps->size = 0;
}
void HeapDestory(Heap* hp)//銷毀堆
{
	assert(hp);
	free(hp->arr);
	hp->cap = hp->size = 0;
}
void Swap(HpDataType* a, HpDataType* b)//交換元素
{
	HpDataType c = *a;
	*a = *b;
	*b = c;
}
void AdjustUP(HpDataType* arr,int child)//向下調(diào)整
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
			break;
	}
}
void AdjustDOWN(HpDataType* arr, int size, int parent)//向上調(diào)整
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (arr[child] > arr[child + 1])
			child++;
		if ((child+1)<size&&arr[child] < arr[parent])
		{
			Swap(&arr[parent], &arr[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}
void HeapPush(Heap* ps, HPDataType x)//插入元素
{
	assert(ps);
	if (ps->size == ps->cap)
	{
		int pnc = ps->cap == 0 ? 4 : 2 * ps->cap;
		HpDataType* pnew = realloc(ps->arr, sizeof(HPDataType)*pnc);
		assert(pnew);
		ps->arr = pnew;
		ps->cap = pnc;
	}
	ps->arr[ps->size] = x;
	ps->size++;
	AdjustUP(ps->arr, ps->size - 1);
}
void HeapPop(Heap* hp)//刪除元素
{
	assert(hp);
	assert(hp->size);
	if (hp->size == 1)
	{
		hp->size--;
		return;
	}
	Swap(&(hp->arr[0]), &(hp->arr[hp->size - 1]));
	hp->size--;
	AdjustDOWN(hp->arr, hp->size, 0);
}
HPDataType HeapTop(Heap* hp)//取堆頂元素
{
	assert(hp);
	assert(hp->size);
	return hp->arr[0];
}
int HeapSize(Heap* hp)//計(jì)算堆元素個(gè)數(shù)
{
	assert(hp);
	return hp->size;
}
int HeapEmpty(Heap* hp)//判斷堆是否為空
{
	assert(hp);
	return hp->size == 0;
}

三.建堆算法

在學(xué)習(xí)建堆算法的時(shí)候我們以對(duì)數(shù)組建堆為例,就是把數(shù)組的數(shù)據(jù)之間的關(guān)系做成一個(gè)堆結(jié)構(gòu),一般有兩種方法,向上調(diào)整建堆和向下調(diào)整建堆,具體怎么做我們來(lái)看下面。

1.向上建堆法

向上建堆法也就是通過(guò)向上調(diào)整建堆,我們拿到一個(gè)數(shù)組后可以把數(shù)組的首元素當(dāng)做堆,第二個(gè)元素當(dāng)做把新的元素插入堆,然后通過(guò)向上調(diào)整構(gòu)成新的堆,以此類推下去把數(shù)組遍歷完后一個(gè)堆就建成了。時(shí)間復(fù)雜度為O(N*logN)

代碼示例:

#include<stdio.h>
#include"Heap.h"
int main()
{
	int arr[] = { 1,9,3,7,6,4,2,10,8,5 };
	int size = sizeof(arr) / sizeof(int);
	for (int i = 0; i < size; i++)
		AdjustUP(arr, i);//該函數(shù)在上文已給出,這里不再展示
	printf("建大堆后:\n");
	for (int i = 0; i < size; i++)
		printf("%d ", arr[i]);
	return 0;
}

 不過(guò)該方法相比向下調(diào)整建堆效率比較低,我們來(lái)看向下調(diào)整建堆法。

2.向下建堆法

向下建堆法也就是通過(guò)向下調(diào)整建堆,要注意并不是從首元素開始調(diào)整,因?yàn)閯傞_始它并不滿足左右子樹都是堆結(jié)構(gòu),所以不能直接從第一個(gè)元素開始向下調(diào)整。既然要滿足左右子樹都是堆那么我們可以考慮從最后一個(gè)元素開始調(diào)整,不過(guò)最后一層下面已經(jīng)沒有元素了,它已經(jīng)是堆,并不用調(diào)整,那么我們從倒數(shù)第二層開始調(diào)整,所以我們先來(lái)計(jì)算一下倒數(shù)第二層最后一個(gè)父節(jié)點(diǎn)的下標(biāo):

(size-1-1)/2

第一個(gè)size-1得到二叉樹的最后一個(gè)元素的下標(biāo),再減一除以二得到它的父節(jié)點(diǎn)的下標(biāo)。

代碼示例:

#include<stdio.h>
#include"Heap.h"
int main()
{
	int arr[] = { 1,9,3,7,6,4,2,10,8,5 };
	int size = sizeof(arr) / sizeof(int);
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)
		AdjustDOWN(arr, size,i);//該函數(shù)在上文已給出,這里不再展示
	printf("建大堆后:\n");
	for (int i = 0; i < size; i++)
		printf("%d ", arr[i]);
	return 0;
}

它的時(shí)間復(fù)雜度為O(N)證明如下: 

其中Sn為總的調(diào)整次數(shù). 

四.堆排序

給一個(gè)數(shù)組建堆后利用堆的性質(zhì)給數(shù)組排序,使其效率更高,這就是一個(gè)堆排序。比如現(xiàn)在要對(duì)一個(gè)數(shù)組進(jìn)行堆排序,第一個(gè)問(wèn)題就是建大堆還是小堆,怎么利用堆來(lái)給數(shù)組排序。

要進(jìn)行升序就需要建大堆,如果建的是小堆,那么堆頂也就是首元素就是最小的元素,并不需要?jiǎng)?,那么?lái)處理第二個(gè)元素就注意到它并不一定是第二小的元素,只能從第二個(gè)元素開始重新建一個(gè)小堆,那么每排一個(gè)元素都需要重新建一個(gè)小堆效率就會(huì)變得很低。

升序建大堆的話,第一個(gè)元素就是最大的元素,我們可以讓它與最后一個(gè)元素互換,然后把堆的元素個(gè)數(shù)減一(就是把最后一個(gè)元素當(dāng)做是堆外),最后把堆頂元素向下調(diào)整,反復(fù)操作直到堆的元素個(gè)數(shù)變?yōu)榱肆?。這樣一個(gè)數(shù)組就按升序排好了。

降序需要建小堆,原理和排升序相同這里就不在贅述。

代碼示例:

#include<stdio.h>
#include"Heap.h"
int main()
{
	int arr[] = { 1,9,3,7,6,4,2,10,8,5 };
	int size = sizeof(arr) / sizeof(int);
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)
		AdjustDOWN(arr, size,i);
	printf("建大堆后:\n");
	for (int i = 0; i < size; i++)
		printf("%d ", arr[i]);
	while (size)
	{
		Swap(&arr[0], &arr[size - 1]);//交換元素
		size--;
		AdjustDOWN(arr, size, 0);
	}
	printf("\n排序后;\n");
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
		printf("%d ", arr[i]);
	return 0;
}

五.在文件中Top出最小的K個(gè)數(shù)

用堆結(jié)構(gòu)的一個(gè)好處就在于,不需要排序就能高效的找出最小的前n個(gè)元素或最大的前n個(gè)元素,現(xiàn)在我們來(lái)利用堆來(lái)嘗試找出文件中最小的K個(gè)數(shù),一個(gè)比較低效的一個(gè)方法就是把文件中涉及到的所以數(shù)據(jù)都取出來(lái)然后把它建成一個(gè)小堆,然后Pop出前k次,得到最小的k個(gè)數(shù)。但是如果這個(gè)數(shù)據(jù)非常的大呢,比如有上億個(gè)數(shù)據(jù),那么就會(huì)消耗很大的內(nèi)存空間。

有一個(gè)很優(yōu)的方法就是只取出文件的前K個(gè)數(shù)建成一個(gè)大堆,也就是說(shuō)這個(gè)堆只用儲(chǔ)K個(gè)元素,那么堆頂就是這個(gè)堆的最大元素,然后繼續(xù)遍歷文件每遍歷一個(gè)元素都與堆頂元素作比較,如果比堆頂元素小就更新一下堆頂元素(把小的那個(gè)變成堆頂元素),然后進(jìn)行向下調(diào)整,直到遍歷完整個(gè)文件,那么此時(shí)堆中的元素就是文件中最小的K個(gè)元素。此方法在時(shí)間復(fù)雜度上與上一方法差不多,但它大大的節(jié)省了空間。

代碼示例:

#include<stdio.h>
#include"Heap.h"
void CreateNDate()
{
	//造數(shù)據(jù),寫入文件中
	int n = 10000;
	srand((unsigned int)time(NULL));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}
	for (int i = 0; i < n; ++i)
	{
		int x = rand() % 1000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
void PrintTopK(int k)
{
	int* arr = (int*)malloc(sizeof(int) * k);
	assert(arr);
	FILE* fop = fopen("data.txt", "r");
	if (!fop)
	{
		perror("fopen error");
		return;
	}
	for (int i = 0; i < k; i++)//先取出k個(gè)建大堆
		fscanf(fop, "%d", &arr[i]);
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
		AdjustDOWN(arr, k, i);
	int x = 0;
	while (fscanf(fop, "%d", &x) != EOF)
	{
		if (arr[0] > x)
		{
			arr[0] = x;
			AdjustDOWN(arr, k, 0);
		}
	}
	for (int i = 0; i < k; i++)//輸出堆中元素
		printf("%d ", arr[i]);
}
int main()
{
	CreateNDate();
	int k = 0;
	scanf("%d", &k);
	PrintTopK(k);
	return 0;
}

到此這篇關(guān)于C語(yǔ)言堆實(shí)現(xiàn)建堆算法和堆排序的文章就介紹到這了,更多相關(guān)C語(yǔ)言 堆 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • C語(yǔ)言中的柔性數(shù)組你了解嗎

    C語(yǔ)言中的柔性數(shù)組你了解嗎

    這篇文章主要為大家詳細(xì)介紹了C99中的新語(yǔ)法——柔性數(shù)組的使用以及優(yōu)缺點(diǎn),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的可以了解一下
    2023-04-04
  • C++實(shí)現(xiàn)迷宮游戲

    C++實(shí)現(xiàn)迷宮游戲

    這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)迷宮游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-03-03
  • VS2019安裝配置MFC(安裝vs2019時(shí)沒有安裝mfc)

    VS2019安裝配置MFC(安裝vs2019時(shí)沒有安裝mfc)

    這篇文章主要介紹了VS2019安裝配置MFC(安裝vs2019時(shí)沒有安裝mfc),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • C語(yǔ)言順序表實(shí)現(xiàn)代碼排錯(cuò)

    C語(yǔ)言順序表實(shí)現(xiàn)代碼排錯(cuò)

    這篇文章主要介紹了C語(yǔ)言順序表實(shí)現(xiàn)方法,大家參考使用吧
    2013-12-12
  • Ubuntu18.04上安裝Qt5.10的步驟實(shí)踐

    Ubuntu18.04上安裝Qt5.10的步驟實(shí)踐

    Qt是一個(gè)跨平臺(tái)的C++圖形用戶界面庫(kù),本文就介紹了Ubuntu18.04上安裝Qt5.10的步驟實(shí)踐,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • c++11中std::move函數(shù)的使用

    c++11中std::move函數(shù)的使用

    本文主要介紹了c++11中std::move函數(shù)的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • C語(yǔ)言程序如何求學(xué)生總成績(jī)和平均成績(jī)

    C語(yǔ)言程序如何求學(xué)生總成績(jī)和平均成績(jī)

    這篇文章主要介紹了C語(yǔ)言程序如何求學(xué)生總成績(jī)和平均成績(jī),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • 用C/C++代碼檢測(cè)ip能否ping通(配合awk和system可以做到批量檢測(cè))

    用C/C++代碼檢測(cè)ip能否ping通(配合awk和system可以做到批量檢測(cè))

    今天小編就為大家分享一篇關(guān)于用C/C++代碼檢測(cè)ip能否ping通(配合awk和system可以做到批量檢測(cè)),小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-04-04
  • 深度剖析C語(yǔ)言結(jié)構(gòu)體

    深度剖析C語(yǔ)言結(jié)構(gòu)體

    今天小編就為大家分享一篇關(guān)于深度剖析C語(yǔ)言結(jié)構(gòu)體,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-12-12
  • C++中浮點(diǎn)類型的具體使用

    C++中浮點(diǎn)類型的具體使用

    C++提供了不同精度的浮點(diǎn)類型,主要有?float、double?和?long?double,這些浮點(diǎn)類型具有不同的字節(jié)大小和范圍,用于滿足不同應(yīng)用場(chǎng)景的精度要求,本文主要介紹了C++中浮點(diǎn)類型的具體使用,感興趣的可以了解一下
    2023-08-08

最新評(píng)論