[Estructura de datos] implementación de almacenamiento dinámico, clasificación de almacenamiento dinámico y problema TOP-K

Tabla de contenido

1. El concepto y la estructura del montón.

2. Implementación del montón

2.1 Inicializar el montón

2.2 Destruir el montón

2.3 Tome el elemento superior del montón

2.4 Devolver el tamaño del montón

2.5 Juzgar si está vacío

2.6 Montón de impresión

2.7 Inserción de elementos

2.8 Escalado del montón

2.9 Elementos emergentes

2.10 Ajuste hacia abajo del montón

3. Complejidad del tiempo de construcción del montón

4. Aplicación del montón

4.1 Clasificación de montón

4.2 Problema TOP-K


1. El concepto y la estructura del montón.

Un montón es una estructura de datos que consta de un conjunto de elementos que se ordenan y se accede a ellos de acuerdo con ciertas reglas. Un montón se puede ver como un árbol binario completo en el que el valor de cada nodo es mayor o igual que su nodo secundario (para un montón máximo) o menor o igual que su nodo secundario (para un montón mínimo) . Los montones se utilizan a menudo para resolver problemas priorizados, como encontrar el elemento más grande o más 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.

2. Implementación del montón

Lo que está escrito aquí es la pila de raíz pequeña, y la pila de raíz grande se puede modificar ligeramente sobre la base de la pila de raíz pequeña. Las siguientes son algunas funciones de interfaz que implementará el montón:

//初始化堆
void HeapInit(HP* php);
//销毁堆
void HeapDestory(HP* php);
//插入元素
void HeapPush(HP* php, HPDataType x);
//堆向上调整算法
void AdjustUp(HP* php, int x);
//弹出堆顶元素
void HeapPop(HP* php);
//堆向下调整算法
void AdjustDwon(HPDataType* a, int size, int x);
//取堆顶元素
HPDataType HeapTop(HP* php);
//返回堆的大小
int HeapSize(HP* php);
//判断是否为空
bool HeapEmpty(HP* php);
//打印堆
void HeapPrint(HP* php);

Definición de montón:

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

Para algunas funciones simples de la interfaz, no las presentaremos en detalle. En el montón, lo que principalmente necesitamos aprender es el algoritmo de ajuste ascendente y el algoritmo de ajuste descendente. Estas dos funciones se llaman cuando se insertan elementos y cuando se abren elementos, respectivamente.

2.1 Inicializar el montón

void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

2.2 Destruir el montón

void HeapDestory(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

2.3 Tome el elemento superior del montón

HPDataType HeapTop(HP* php)
{
	assert(php);
	return php->a[0];
}

2.4 Devolver el tamaño del montón

int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

2.5 Juzgar si está vacío

bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

2.6 Montón de impresión

void HeapPrint(HP* php)
{
	assert(php);
	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

2.7 Inserción de elementos

Inserte un elemento en el montón, podemos insertar este elemento al final del montón, porque la estructura de almacenamiento real del montón es una matriz, podemos poner el elemento al final de la matriz, pero si solo se inserta en Al final de la matriz, el montón será Para romper el ciclo, también debemos llamar a una función de ajuste ascendente para ajustar la relación de tamaño entre cada nodo.

Antes de insertar, es necesario juzgar si la capacidad del montón es suficiente. Si la capacidad del montón está llena, debe expandirse. Aquí, cada expansión se duplica en realidad sobre la base original.

void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}

	php->a[php->size] = x;
	AdjustUp(php->a, php->size);//向上调整
	php->size++;
}

2.8 Escalado del montón

En el proceso de inserción de elementos anterior, hemos utilizado el algoritmo de ajuste ascendente del montón. A continuación, echemos un vistazo a cómo implementar este algoritmo de ajuste ascendente:

Primero inserte un 10 al final de la matriz y luego realice un algoritmo de ajuste hacia arriba hasta que se satisfaga el montón.

Proceso gráfico:

void AdjustUp(HPDataType* a, int x)
{
	int child = x;
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
		}
		else
		{
			break;
		}
		child = parent;
		parent = (child - 1) / 2;
	}
}

Análisis de código: 

  1. Inicialice la variable hijo como nodo x y el padre como el índice de su nodo padre, es decir, (hijo - 1) / 2.
  2. Ingrese un bucle que se ejecutará hasta que el nodo x flote hasta una posición adecuada o alcance la parte superior del montón.
  3. En el ciclo, juzgue si el valor del nodo x es menor que el valor de su nodo principal y, de ser así, intercambie los valores de los dos.
  4. Si el valor del nodo x no es menor que el valor del nodo principal, salga del bucle, porque la naturaleza del montón se cumple en este momento.
  5. Actualice los valores de child y parent, actualice child to parent y actualice parent en el índice de su nodo principal, es decir, (child - 1) / 2.
  6. Repita los pasos 3 a 5 hasta que el valor del nodo x sea mayor o igual que el valor de su nodo principal, o hasta que se alcance la parte superior del montón.

2.9 Elementos emergentes

Extraer un elemento es eliminar el elemento en la parte superior del montón, pero no podemos eliminarlo directamente, lo que destruiría la estructura del montón. La forma correcta es intercambiar primero el elemento en la parte superior del montón con el último elemento. , de modo que el primer elemento está garantizado. El subárbol izquierdo y el subárbol derecho todavía tienen la forma de un montón, y luego se reduce el tamaño y, finalmente, se llama a una función de ajuste hacia abajo del montón.

void HeapPop(HP* php)
{
	assert(php);
	Swap(&php->a[0], &php->a[php->size-1]);
	php->size--;
	AdjustDwon(php->a, php->size, 0);
}

2.10 Ajuste hacia abajo del montón

Ajuste hacia abajo del montón: cada vez que se intercambia el valor más pequeño del nodo padre y los hijos izquierdo y derecho (pequeño montón raíz), el valor del nodo hijo del nodo padre se actualiza continuamente.

void AdjustDwon(HPDataType* a, int size, int x)
{
	int parent = x;
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && a[child + 1] < a[child])
		{
			child++;
		}
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
		}
		else
		{
			break;
		}
		parent = child;
		child = parent * 2 + 1;
	}
}
  1. Inicialice la variable padre como nodo x y el hijo como el índice de su nodo hijo izquierdo, es decir, padre * 2 + 1.
  2. Ingrese un bucle, que se ejecutará hasta que el nodo x se hunda en una posición adecuada o no tenga nodos secundarios.
  3. En el ciclo, primero juzgue si el nodo x tiene un nodo secundario derecho, y si el valor del nodo secundario derecho es menor que el valor del nodo secundario izquierdo, si es verdadero, actualice child al índice del nodo secundario derecho.
  4. Luego, juzgue si el valor del nodo x es mayor que el valor de sus nodos secundarios y, de ser así, intercambie los valores de los dos.
  5. Si el valor del nodo x no es mayor que el valor del nodo secundario, salga del ciclo, porque la naturaleza del montón se cumple en este momento.
  6. Actualice los valores de padre e hijo, actualice padre a hijo y actualice hijo al índice del nodo hijo izquierdo del padre, es decir, padre * 2 + 1.
  7. Repita los pasos 3 a 6 hasta que el valor del nodo x sea menor o igual que el valor de sus hijos, o no tenga hijos.

3. Complejidad del tiempo de construcción del montón

Debido a que el montón es un árbol binario completo, y el árbol binario completo también es un árbol binario completo, aquí usamos un árbol binario completo para demostrarlo por simplicidad (la complejidad temporal es originalmente una aproximación, y algunos nodos más no afectarán el resultado final):

Ajustar hacia abajo:

Por lo tanto: la complejidad temporal de ajustar el montón hacia abajo es O(N).

Ajustar hacia arriba:

 Por lo tanto: la complejidad temporal de ajustar el montón hacia arriba es N*logN;

4. Aplicación del montón

4.1 Clasificación de montón

Use el montón para ordenar la matriz e imprimirla:

void testHeapSort()
{
	HP hp;
	HeapInit(&hp);

	int a[] = { 1,4,7,5,10,2,8,9,3,6 };
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		HeapPush(&hp, a[i]);
	}
	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	//释放内存
	HeapDestory(&hp);
}
int main()
{
	testHeapSort();
	return 0;
}

Resultado de salida:

 Sin embargo, ¿no es un poco complicado usar este método? Si queremos realizar una clasificación de montón, primero debemos escribir una estructura de datos de montón. Por supuesto, este no es el caso. Podemos modificar el código y construir un montón en la matriz original:

Ideas:

Para construir un montón en la matriz original, podemos usar dos métodos:

El primero es construir el montón. La complejidad de tiempo para construir el montón es O(N*logN). No recomendamos este método.

El segundo es construir un montón hacia abajo, su complejidad temporal es O(N) y su eficiencia es mayor que la de construir un montón hacia arriba. Recomendamos usar la construcción de montones hacia abajo.

Otro punto que es más difícil de entender es: si queremos realizar un orden ascendente, necesitamos construir un montón grande, y si queremos realizar un orden descendente, necesitamos construir un montón pequeño.

void swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}
void HeapSort(int* a, int n)
{
	//从倒数第一个非叶子节点开始调
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDwon(a, n, i);//向下调整建堆
	}
	int end = n - 1;
	while (end > 0)
	{
		swap(&a[0], &a[end]);
		AdjustDwon(a, end, 0);//向下调整[0,end]的元素
		--end;
	}
}
int main()
{
	int a[] = { 1,4,7,5,10,2,8,9,3,6 };
	int n = sizeof(a) / sizeof(a[0]);
	HeapSort(a,n);//堆排序
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

4.2 Problema TOP-K

Problema TOP-K: Encuentre los K elementos más grandes o los elementos más pequeños en la combinación de datos Generalmente, la cantidad de datos es relativamente grande.

Por ejemplo: los 10 mejores jugadores profesionales, los 500 mejores del mundo, la lista completa, los 100 mejores jugadores activos en el juego, etc.

Para el problema de Top-K, la forma más sencilla y directa que se puede pensar es la clasificación, pero: si la cantidad de datos es muy grande, la clasificación no es recomendable (puede que no sea posible cargar todos los datos en la memoria al mismo tiempo). una vez). La mejor manera es usar el montón para resolverlo.La idea básica es la siguiente:

1. Use los primeros elementos K en el conjunto de datos para construir un montón

  • Para los primeros k elementos más grandes, construye un montón pequeño
  • Para los primeros k elementos más pequeños, construye un montón grande

2. Use los elementos NK restantes para compararlos con los elementos superiores a su vez, y reemplace los elementos superiores si no están satisfechos

Después de comparar los elementos NK restantes con los elementos superiores del montón, los elementos K restantes en el montón son los primeros K elementos más pequeños o más grandes buscados.

Aplicación práctica: encuentre los diez números más grandes entre 10000000 números aleatorios

void AdjustDwon(int* a, int size, int x)
{
	int parent = x;
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && a[child + 1] < a[child])
		{
			child++;
		}
		if (a[child] < a[parent])
		{
			int tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;
		}
		else
		{
			break;
		}
		parent = child;
		child = parent * 2 + 1;
	}
}

void PrintTopK(int* a, int n, int k)
{
	int* KMaxHeap = (int*)malloc(sizeof(int) * k);
	assert(KMaxHeap);
	for (int i = 0; i < k; i++)
	{
		KMaxHeap[i] = a[i];
	}
	//建小根堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDwon(KMaxHeap, k, i);
	}
	//依次比较a数组中剩余的元素
	for (int i = k; i < n; i++)
	{
		if (a[i] > KMaxHeap[0])
		{
			KMaxHeap[0] = a[i];
		}
		AdjustDwon(KMaxHeap, k, 0);
	}
	//打印
	for (int i = 0; i < k; i++)
	{
		printf("%d ", KMaxHeap[i]);
	}
}
void testTopK()
{
	srand(time(0));
	int n = 10000000;
	int* a = (int*)malloc(sizeof(int) * n);
	for (int i = 0; i < n; i++)
	{
		a[i] = rand() % n;//a[i]的范围[1,n]
	}
	//手动设定10个最大的数
	a[2] = n + 3;
	a[122] = n + 5;
	a[1233] = n + 1;
	a[12333] = n + 2;
	a[1322] = n + 8;
	a[2312] = n + 6;
	a[54612] = n + 7;
	a[546612] = n + 9;
	a[5612] = n + 10;
	a[46612] = n + 4;
	PrintTopK(a, n, 10);
}
int main()
{
	testTopK();
	return 0;
}

Supongo que te gusta

Origin blog.csdn.net/m0_73648729/article/details/132268305
Recomendado
Clasificación