Montón (dos métodos para construir un montón), clasificación de montón y problemas Top-K

montón


是一种完全二叉树,分为大堆,小堆

Si hay un conjunto de códigos clave int K[ ] = {27,15,19,18,28,34,65,49,25,37}, almacene todos sus elementos
en una matriz de una dimensión y satisfaga: K i <=K 2*i+1 y K i <=K 2*i+2 ( K i >=K 2*i+1 y K i >=K 2*i+2 ) i = 0, 1 ,
2..., entonces se llama montón pequeño (o montón grande). El montón con el nodo raíz más grande se denomina montón más grande o montón raíz grande, y el montón con el nodo raíz más pequeño se denomina montón más pequeño o montón raíz pequeño.

Propiedades del montón:

  • El valor de un nodo en el montón siempre no es mayor ni menor que el valor de su nodo principal;
  • El montón es siempre un árbol binario completo.

inserte la descripción de la imagen aquí

La manera de construir un montón

有两种,1.向上建堆,2.向下建堆

construir

时间复杂度为:O(N*logN)

typedef int HPDataType;
//交换函数
void Swap(HPDataType* a, HPDataType* b) {
    
    
	HPDataType temp = *a;
	*a = *b;
	*b = temp;
}
//向上调整的条件是:调整的child结点之前的结点应该也为大堆/小堆,所以,向上调整建堆的时候,是从数组的第一个元素开始
void ADjustUp(HPDataType* a, int child) {
    
    
	//向上调整
	int parent = (child - 1)/2;
	while (child >0) //child!=0
	{
    
    
		if (a[child] > a[parent]) 
		{
    
    
			Swap(&a[child], &a[parent]);//取地址进行交换
			//进行交换
			child = parent;
			parent = (child - 1) /2;
		}
		else {
    
    
			break;
		}
		
	}
}
//向上建堆
//for (int i = 0; i < n; i++) {
    
    
//	ADjustUp(arr, i);
//}
int main(){
    
    
    int arr[]={
    
    23,23,1,32,12,3,12,31,324};
    int n=sizeof(arr)/sizeof(arr[0]);//得到数组的个数大小
    //n-1表示为数组最后一个元素的下标,(n-1-1)/2得到其父节点,从父结点开始,向下调整
	for (int i = 0; i < n; i++) {
    
    
		ADjustUp(arr, i);
    }
}

construir hacia abajo

时间复杂堆为:O(N)

typedef int HPDataType;
//交换函数
void Swap(HPDataType* a, HPDataType* b) {
    
    
	HPDataType temp = *a;
	*a = *b;
	*b = temp;
}
//向下调整建堆的条件为:左右子树都为大堆/小堆,所以从最后一个结点开始,又因为如果是叶子结点的话,本身调整是没有意义的,所以就从倒数第二层开始
void AdjustDown(HPDataType* a, int n, int parent) {
    
    
	int child = parent * 2 + 1;//先找到左孩子的位置
	while (child < n) {
    
    
		//从左右孩子里面选一个
		if (child + 1 < n && a[child + 1] > a[child]) {
    
    
			//如果右孩子存在,且,右孩子大于左孩子
			child++;//
		}
		if (a[child] > a[parent]) {
    
    
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else {
    
    
			break;
		}
	}
}
//向下建堆
int main(){
    
    
    int arr[]={
    
    23,23,1,32,12,3,12,31,324};
    int n=sizeof(arr)/sizeof(arr[0]);//得到数组的个数大小
    //n-1表示为数组最后一个元素的下标,(n-1-1)/2得到其父节点,从父结点开始,向下调整
 	for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
    
    
		AdjustDown(arr, n, i);
	}   
}

Calcular la complejidad temporal de las dos formas.

我们可以看到的是向下调整建堆时间复杂度更小,我们通过每一层的每一个结点的最多需要调整的次数为依据,假设高度为h,如果是向上调整的话,最后一个结点(最后一层,也就是2^(h-1)个结点),最多调整h-1次,向上的时候需要调整的每一层结点变少,层数变少,所以最后肯定是总调整次数更多

Como se muestra en la figura: Esta figura puede resolver este problema desde un punto de vista matemático

inserte la descripción de la imagen aquí

ordenar en montón

采用向上或者是向下调整的方法,最后实现排序,最后的时间复杂度为:O(N*logN)

//所以不管是向上还是向下建堆,时间复杂度都是为O(N*logN)
void HeapSort(int arr[],int n){
    
    
    //比较偏向于向下调整建堆 时间复杂度:O(N)
    for(int i=(n-1-1)/2;i>=0;i--){
    
    
        AdjustDown(arr,n,i);
    }
    //向上建堆,时间复杂度为 O(N*logN)
    //for (int i = 0; i < n; i++) {
    
    
	//	ADjustUp(arr, i);
	//}
    //进行排序,如果想要升序,那就建大堆,然后进行向下调整
    int end = n - 1;//从最后一个结点开始
	while (end>0) {
    
    
		Swap(&arr[end], &arr[0]);
		AdjustDown(arr, end, 0);//向下调整
		end--;
	}
	for (int i = 0; i < n; i++) {
    
    
		printf("%d ", arr[i]);
	}
}
int main()
{
    
    
    int arr[]={
    
    2,231,2,3,12,312,3,123,423,4,2};
    int n=sizeof(arr)/sizeof(arr[0]);
    HeapSort(arr,n);
    return0;
}

Preguntas principales

Top-K问题:从N个数据中找出前k个最大/最小的数

当然,在N的数值不是很大的时候,我们可以使用堆排序的方法,找到对应的前k个数值,但是当N的个数为10亿,或者是100亿的时候,这个数据的处理量就不能用原本的简单的堆排序方法啦

La idea es la siguiente

Si se trata de un volumen de datos de 10 000 millones, podemos saber a partir de la siguiente conversión de memoria que el volumen de datos de 10 000 millones de enteros es casi 40 G, por lo que no se puede calcular simplemente mediante la clasificación de montones

La nueva idea es la siguiente:

  • Cree una matriz de tamaño k y construya los primeros valores k en un montón pequeño
  • Finalmente, recorra los valores restantes y ajuste hacia abajo para cada valor (si el valor es mayor que el nodo superior, reemplácelo y ajústelo hacia abajo)
  • Los datos del último montón pequeño son los k superiores más grandes
void AdjustDown(int* a, int n,int parent) {
    
    
    int child = parent * 2 + 1;//先找到左孩子
    while (child < n) {
    
    //对于小堆的向下调整
        if (child + 1 < n && a[child + 1] < a[child]) {
    
    
            //如果右孩子大于左孩子,那么就进行交换
            child++;
        }
        if (a[child] < a[parent]) {
    
    
            int temp = a[child];
            a[child] = a[parent];
            a[parent] = temp;
            parent = child;
            child = parent * 2 + 1;
        }
        else {
    
    
            break;
        }
    }
}
void PrintTopK(const char*file,int k){
    
    
    //输出前k个数值
    //1.我们先创建为k个数据的小堆
    int*topK=(int*)malloc(sizeof(int)*k);
    assert(topK);
    FILE*fout=fopen(file,"r");//读取文件 file
    if(fout==NULL){
    
    
        perror("open fail");
        return;
    }
    
    //读取前k个数值做小堆
    for(int i=0;i<k;i++){
    
    
        fscanf(fout,"%d",&topK[i]);//读取前k个数值在数组topK中
    }
    for(int i=(k-2)/2;i>=0;i--){
    
    
        AdjustDown(topK,k,i);//将topK数组建立小堆
    }
    
    //2.将剩余的n-k个元素依次于对顶元素进行交换,不满则替换
    int val=0;
    int ret=fscanf(fout,"%d",&val);
    while(ret!=EOF){
    
    //如果文件中的数值全部被读取,那么就会返回EOF
        if(val>topK[0]){
    
    
            topK[0]=val;//直接赋值即可,不必要交换
            AdjustDown(topK,k,0);//向下调整
        }
        ret=fscanf(fout,"%d",&val);
    }
    for(int i=0;i<k;i++){
    
    
        printf("%d",topK[i]);//打印topK数组中k个数值,这就是文件中n个数值中最大的前k个数
    }
    print("\n");
    free(topK);
    fclose(fout);
}
void CreateNData(){
    
    
    //创建N个数据
    int n=1000000;
    srand(time(0));//使用随机数前要i使用这个代码
    const char*file="data.txt";
    FILE*fin=fopen(file,"w");
    if(fin==NULL){
    
    
        perror("fopen fail");
        return;
    }
    
    for(int i=0;i<n;i++){
    
    
        int x=rand()%10000;
        fprintf(fin,"%d\n",x);//读写n个数据在fin所对应的“data.txt”文件中
    }
    fclose(fin);//关闭文件,这样才能正确的在硬盘文件中“data.txt”存储数值
}
int main()
{
    
    
    CreateNData();
    PrintTopK("data.txt",10);//表示从文件data.txt中读取前10个最大的数值
    return 0;
}

实验结果如下:成功读取出来1000000随机数中前k个最大的数字

inserte la descripción de la imagen aquí

Este artículo trata principalmente sobre la definición del montón, los dos métodos de creación del montón, la construcción del montón y la construcción del montón, la realización de una demostración de código, el análisis de la complejidad temporal del mismo y la realización de la clasificación del montón. , la clasificación del montón es para construir el montón primero (puede ser hacia arriba o hacia abajo, se recomienda bajar), luego use el bucle while, bucle n-1 veces para intercambiar la cabeza y la cola, y luego ajuste hacia abajo. Hay explicaciones detalladas arriba y, finalmente, una explicación del problema del montón Top-K. Y adjunte el código para la demostración.

Supongo que te gusta

Origin blog.csdn.net/qq_63319459/article/details/129976643
Recomendado
Clasificación