堆结构和堆排序

堆是一种特殊的完全二叉树,其他语言中的优先级队列就是堆。堆分为大根堆小根堆,大根堆即对于每一颗树,它的父亲节点的值,一定大于它的孩子节点的值,左右节点的值不用管它的顺序。小根堆同理。

堆的实现通常是用数组实现的,那么对于每一个节点在数组中怎么找到它的父节点和它的左右孩子就成了一个问题。

那么对于任意一个节点i,它的父节点在数组中的表示为(i - 1) / 2,它的左孩子为2 * i + 1,它的右孩子为2 * i + 2

那么堆最主要的就两个操作,在堆中插入一个节点和在堆中删掉一个节点。

最小堆的ADT代码实现

//最小堆的顺序实现 
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#define u unsigned
#define ll long long
#define sc scanf
#define pr printf 
#define fr(i, j, n) for (int i = j; i < n; i++)
#define N 1000001
#define ERROR -1

typedef int ELEMENT;

typedef struct heap {
	ELEMENT* data;//存放元素的数组 
	int size;//最小堆中的元素个数 
	int maxSize;//堆中最多能够容纳的元素个数 
}minHeap, *MinHeap;

MinHeap createMinHeap(int n);//创建一个最多能够容纳n个元素的最小堆 
int MinHeapSize(MinHeap h);//返回最小堆的元素个数 
bool isMinHeapFull(MinHeap h);//判断最小堆是不是满了 
bool addMinHeap(MinHeap h, ELEMENT item);//往最小堆里面添加元素item
ELEMENT deleteMinHeap(MinHeap h);//弹出最小堆的堆顶元素 
void MinHeapInsert(MinHeap h, int index);//在最小堆index位置新进来一个元素,所以要向上调整,来让堆仍然是一个最小堆 
void MinHeapify(MinHeap h, int index);//在最小堆index位置的元素变小了,所以向下调整堆,来让堆仍然是一个最小堆 
bool isMinHeapEmpty(MinHeap h);//判断最小堆空了没有 
void swap(MinHeap h, int i, int j);//交换最小堆中i和j位置的元素 
int MinHeapCompare(ELEMENT a, ELEMENT b);//基于某中规则来比较,类似与比较器 
void freeMinHeap(MinHeap h);//释放最小堆的空间 
ELEMENT peekMinHeap(MinHeap h);

int main(int argc, char* argv[])
{
	MinHeap h = createMinHeap(3);

	addMinHeap(h, 2);
	addMinHeap(h, 1);
	addMinHeap(h, 3);
	
	pr("min = %d\n", peekMinHeap(h)); 
	pr("MinHeapSize = %d\n", MinHeapSize(h));
	pr("%d\n", deleteMinHeap(h));
	pr("%d\n", deleteMinHeap(h));
	pr("MinHeapSize = %d\n", MinHeapSize(h));
	
	return 0;
}
MinHeap createMinHeap(int n)
{
	MinHeap h = (MinHeap)malloc(sizeof(minHeap));//开辟一个最小堆 

	h -> data = (ELEMENT*)malloc(sizeof(ELEMENT) * n);//开辟一个n个空间的ELEMENT数组 
	h -> size = 0;//初始化最小堆元素是0 
	h -> maxSize = n;//最小堆能存放最大元素的个数是n 

	return h;//返回最小堆 
}
int MinHeapSize(MinHeap h)
{
	return h -> size;//返回最小堆中的元素个数 
}
bool isMinHeapFull(MinHeap h)
{
	return h -> size == h -> maxSize;//如果最小堆中的元素个数等于能够容纳的最大元素个数,那么满了 
}
bool addMinHeap(MinHeap h, ELEMENT item)
{
	if (isMinHeapFull(h)) {//如果最小堆已经满了,那么就不能添加元素了,返回false 
		return false;
	}

	h -> data[h -> size] = item;//把item放在最下堆中元素个数的下标中,因为元素个数的下标就是要放的位置 
	MinHeapInsert(h, h -> size);//因为刚刚新进来一个元素,所以想让堆继续保持最小堆,那么就需要向上调整堆 
	h -> size++;//让最小堆中的元素个数加一 
	
	return true;//添加成功返回true 
}
ELEMENT deleteMinHeap(MinHeap h)
{
	if (isMinHeapEmpty(h)) {//如果最小堆中没有元素,那么不能进行删除,违法返回一个特定的值,即ERROR 
		return ERROR;
	}

	ELEMENT ans = h->data[0];//返回最小堆中的堆顶元素 
	swap(h, 0, --h->size);//把最小堆中的最后一个元素与堆顶元素进行交换,并且让堆中元素减一,那么就访问不到刚刚被删除的元素了。 
	MinHeapify(h, 0);//下标0位置的元素变大了,那么为了继续保持一个最小堆,就要向下调整 

	return ans;//返回最小堆中的堆顶元素 
}
void MinHeapInsert(MinHeap h, int index)//index位置的数向上调整小根堆 
{
	while (MinHeapCompare(h->data[index], h->data[(index - 1) / 2]))//如果下标index的值小于它的父节点 
	{
		swap(h, index, (index - 1) / 2);//下标index的值和它的父节点的值交换 
		index = (index - 1) / 2;//更新index的位置 
	}
}
bool isMinHeapEmpty(MinHeap h)
{
	return !h->size;//如果最小堆中的元素是0个,那么就是空,有元素就不是空 
}
void swap(MinHeap h, int i, int j)
{
	ELEMENT t = h->data[i];
	h->data[i] = h->data[j];
	h->data[j] = t;
}
void MinHeapify(MinHeap h, int index)
{
	int left = 2 * index + 1;//计算下标index的左孩子 

	while (left < h->size) {//如果有孩子 
		int lessest = left + 1 < h->size && MinHeapCompare(h->data[left + 1], h->data[left]) ? left + 1 : left;//如果有右孩子并且右孩子小于左孩子,那么孩子中最小的就是右孩子,否则就是左孩子 

		lessest = MinHeapCompare(h->data[index], h->data[lessest]) ? index : lessest;//如果父亲节点比孩子中最小的还要小,那么最小的节点就是父节点,不然最小的节点就是孩子中最小的节点 

		if (lessest == index) {//如果最小的节点是父节点,那么不用继续了,已经是最小堆了 
			break;
		}

		swap(h, index, lessest);//与最小的孩子进行交换 
		index = lessest;//更新index下标,因为刚刚交换了 
		left = 2 * index + 1;//重新计算下标index的左孩子 
	}
}
int MinHeapCompare(ELEMENT a, ELEMENT b)
{
	return a < b;//直接进行值的比较 
}
void freeMinHeap(MinHeap h)
{
	free(h -> data);//先释放最小堆存放元素的空间 
	free(h);//再释放最小堆的空间 
}
ELEMENT peekMinHeap(MinHeap h)
{
	if (isMinHeapEmpty(h)) {//最小堆里面没有元素那么,操作违法,返回特定值 
		return ERROR;
	}
	
	ELEMENT ans = deleteMinHeap(h);//弹出最小堆的堆顶 
	
	addMinHeap(h, ans);//再放入最小堆,以此来实现代码复用 
	
	return ans;//返回最小堆的堆顶元素 
}

最大堆的ADT代码实现

//最大堆的顺序实现 
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#define u unsigned
#define ll long long
#define sc scanf
#define pr printf 
#define fr(i, j, n) for (int i = j; i < n; i++)
#define N 1000001
#define ERROR -1

typedef int ELEMENT;

typedef struct heap {
	ELEMENT* data;//存放元素的数组 
	int size;//最大堆的元素个数 
	int maxSize;//最大堆最多能够容纳的元素个数 
}maxHeap, * MaxHeap;

MaxHeap createMaxHeap(int n);//创建一个最多能够容纳n个元素的最大堆 
int MaxHeapSize(MaxHeap h);//返回最大堆的元素个数
bool isMaxHeapFull(MaxHeap h);//判断最大堆是不是满了 
bool addMaxHeap(MaxHeap h, ELEMENT item);//往最大堆里面添加元素item
ELEMENT deleteMaxHeap(MaxHeap h);//弹出最大堆的堆顶元素 
void MaxHeapInsert(MaxHeap h, int index);//在最大堆index位置新进来一个元素,所以要向上调整,来让堆仍然是一个最大堆 
void MaxHeapify(MaxHeap h, int index);//在最大堆index位置的元素变小了,所以向下调整堆,来让堆仍然是一个最大堆 
bool isMaxHeapEmpty(MaxHeap h);//判断最大堆空了没有 
void swap(MaxHeap h, int i, int j);//交换最大堆中i和j位置的元素 
int MaxHeapCompare(ELEMENT a, ELEMENT b);//基于某中规则来比较,类似与比较器
void freeMaxHeap(MaxHeap h);//释放最大堆的空间 
ELEMENT peekMaxHeap(MaxHeap h);//返回最大堆的堆顶元素但不弹出 

int main(int argc, char* argv[])
{
	MaxHeap h = createMaxHeap(3);

	addMaxHeap(h, 2);
	addMaxHeap(h, 1);
	addMaxHeap(h, 3);
	
	pr("max = %d\n", peekMaxHeap(h));
	pr("MinHeapSize = %d\n", MaxHeapSize(h));
	pr("%d\n", deleteMaxHeap(h));
	pr("%d\n", deleteMaxHeap(h));
	pr("MinHeapSize = %d\n", MaxHeapSize(h));
	
	return 0;
}
MaxHeap createMaxHeap(int n)
{
	MaxHeap h = (MaxHeap)malloc(sizeof(maxHeap));//开辟一个最大堆 

	h -> data = (ELEMENT*)malloc(sizeof(ELEMENT) * n);//开辟一个可以存放n个元素的数组 
	h -> size = 0;//初始化最大堆元素是0 
	h -> maxSize = n;//最大堆能存放最大元素的个数是n

	return h;//返回最大堆 
}
int MaxHeapSize(MaxHeap h)
{
	return h->size;//返回最大堆中的元素个数
}
bool isMaxHeapFull(MaxHeap h)
{
	return h->size == h->maxSize;//如果最大堆中的元素个数等于能够容纳的最大元素个数,那么满了
}
bool addMaxHeap(MaxHeap h, ELEMENT item)
{
	if (isMaxHeapFull(h)) {//如果最大堆已经满了,那么就不能添加元素了,返回false 
		return false;
	}

	h->data[h->size] = item;//把item放在最下堆中元素个数的下标中,因为元素个数的下标就是要放的位置 
	MaxHeapInsert(h, h->size);//因为刚刚新进来一个元素,所以想让堆继续保持最大堆,那么就需要向上调整堆 
	h->size++;//让最大堆中的元素个数加一 
	
	return true;//添加成功返回true 
}
ELEMENT deleteMaxHeap(MaxHeap h)
{
	if (isMaxHeapEmpty(h)) {//如果最大堆中没有元素,那么不能进行删除,违法返回一个特定的值,即ERROR
		return ERROR;
	}

	ELEMENT ans = h->data[0];//返回最大堆中的堆顶元素 
	swap(h, 0, --h->size);//把最大堆中的最后一个元素与堆顶元素进行交换,并且让堆中元素减一,那么就访问不到刚刚被删除的元素了。
	MaxHeapify(h, 0);//下标0位置的元素变小了,那么为了继续保持一个最大堆,就要向下调整 

	return ans;//返回最大堆中的堆顶元素 
}
void MaxHeapInsert(MaxHeap h, int index)//index位置的数向上调整大根堆 
{
	while (MaxHeapCompare(h->data[index], h->data[(index - 1) / 2]))//如果下标index的值大于它的父节点 
	{
		swap(h, index, (index - 1) / 2);//下标index的值和它的父节点的值交换 
		index = (index - 1) / 2;//更新index的位置 
	}
}
bool isMaxHeapEmpty(MaxHeap h)
{
	return !h->size;//如果最大堆中的元素是0个,那么就是空,有元素就不是空 
}
void swap(MaxHeap h, int i, int j)
{
	ELEMENT t = h->data[i];
	h->data[i] = h->data[j];
	h->data[j] = t;
}
void MaxHeapify(MaxHeap h, int index)
{
	int left = 2 * index + 1;//计算下标index的左孩子 

	while (left < h->size) {//如果有孩子 
		int largest = left + 1 < h->size && MaxHeapCompare(h->data[left + 1], h->data[left]) ? left + 1 : left;//如果有右孩子并且右孩子大于左孩子,那么孩子中最大的就是右孩子,否则就是左孩子

		largest = MaxHeapCompare(h->data[index], h->data[largest]) ? index : largest;//如果父亲节点比孩子中最大的还要大,那么最大的节点就是父节点,不然最大的节点就是孩子中最大的节点 

		if (largest == index) {//如果最大的节点是父节点,那么不用继续了,已经是最大堆了
			break;
		}

		swap(h, index, largest);//与最大的孩子进行交换 
		index = largest;//更新index下标,因为刚刚交换了
		left = 2 * index + 1;//重新计算下标index的左孩子
	}
}
int MaxHeapCompare(ELEMENT a, ELEMENT b)
{
	return a > b;//直接进行值的比较
}
void freeMaxHeap(MaxHeap h)
{
	free(h -> data);//先释放最大堆存放元素的空间 
	free(h);//再释放最大堆的空间 
}
ELEMENT peekMaxHeap(MaxHeap h)
{
	if (isMaxHeapEmpty(h)) {//最大堆里面没有元素那么,操作违法,返回特定值 
		return ERROR;
	}
	
	ELEMENT ans = deleteMaxHeap(h);//弹出最大堆的堆顶 
	
	addMaxHeap(h, ans);//再放入最大堆,以此来实现代码复用 
	
	return ans;//返回最大堆的堆顶元素 
}

看代码我们发现最大堆和最小堆的区别就是比较的规则是不同的其他全是一样的。

堆排序

堆排序的时间复杂度是O(N * logN),它就是把数组转化成一个堆之后,再每次把堆的第一个元素放在一个需要放的位置,然后调整堆。当堆中只有一个元素之后就已经排好序了。

也就是分为了两个步骤,第一步建立一个堆,第二步在删除堆顶元素之后调整堆。

调整过程的时间复杂度是O(N * logN),这是无法再改变的。

但建立堆的是时间复杂度可以做到O(N),建立堆分为从顶建堆和从底建堆。

从顶建堆的时间复杂度是O(N * logN)。

//从顶建堆
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#define N 100000

typedef int ELEMENT;

void heapSort(int *a, int size);
void heapinsert(int *a, int index);
void heapify(int *a, int index, int heapSize);
void swap(int *a, int i, int j);

int main() {
    int a[N];
    
    srand(time(NULL));
    
    int n;
	
	scanf("%d", &n);
    
    for (int i = 0; i < N; i++){
		scanf("%d", a + i);
	}
	
	heapSort(a, n);

	for (int i = 0; i < n; i++){
    	printf("%d ", a[i]);
	}
	
	putchar('\n');

    return 0;
}
void heapSort(ELEMENT *a, int size)
{
	if (a == NULL || size == 2){
		return ;
	}
	
	int heapSize = size;
	
	for (int i = 0; i < size; i++)//从顶建立一个大根堆 
	{
		heapinsert(a, i);
	}
	
	swap(a, 0, --heapSize);//把堆的第一个元素与堆的最后一个元素交换,然后堆的大小减一 
	
	while(heapSize > 1){//只要堆中的元素大于1 
		heapify(a, 0, heapSize);//因为0位置的数变小了,向下调整大根堆 
		swap(a, 0, --heapSize);//把堆的第一个元素与堆的最后一个元素交换,然后堆的大小减一 
	}
}
void heapinsert(ELEMENT *a, int index)//index位置的数向上调整大根堆 
{
	while(a[index] > a[(index - 1) / 2])//如果下标index的值大于它的父节点 
	{
		swap(a, index, (index - 1) / 2);//下标index的值和它的父节点的值交换 
		index = (index - 1) / 2;//更新index的位置 
	}
}
void heapify(ELEMENT *a, int index, int heapSize)//index位置的数变小了,又想维持大根堆结构,向下调整大根堆 
{
	int left = 2 *index + 1;//下标index的左孩子 
	
	while(left < heapSize)//只要还有孩子 
	{
		int largest = left + 1 < heapSize && a[left + 1] > a[left]? left + 1: left;//如果有右孩子并且右孩子的值大于左孩子的值,那么右孩子就是最大的,否则就是左孩子最大 
		
		largest = a[largest] > a[index]? largest:index;//如果左右孩子中最大的比父节点大,那么最大的还是它,没有父节点大,那么最大的是父节点 
		
		if (largest == index) {//如果最大的是父节点,那么不用调整了,是大根堆了 
			break;
		}
		
		swap(a, largest, index);//父节点与左右孩子中最大的做交换 
		index = largest;//更新index 
		left = index * 2 + 1;//重新计算左孩子的位置 
	}
}
void swap(ELEMENT *a, int i, int j)
{
	ELEMENT t = a[i];
	a[i] = a[j];
	a[j] = t;
}

从底建堆的时间复杂度是O(N),因为它调整的次数变少了。

代码实现

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#define N 100000

typedef int ELEMENT;

void heapSort(int *a, int size);
void heapinsert(int *a, int index);
void heapify(int *a, int index, int heapSize);
void swap(int *a, int i, int j);

int main() {
    int a[N];
    
    srand(time(NULL));
    
    int n;
	
	scanf("%d", &n);
    
    for (int i = 0; i < N; i++){
		scanf("%d", a + i);
	}
	
	heapSort(a, n);

	for (int i = 0; i < n; i++){
    	printf("%d ", a[i]);
	}
	
	putchar('\n');

    return 0;
}
void heapSort(ELEMENT *a, int size)
{
	if (a == NULL || size == 2){
		return ;
	}
	
	int heapSize = size;
	
	for (int i = size - 1; i >= 0; i--)//从底建立一个大根堆 
	{
		heapify(a, i, size);
	}
	
	swap(a, 0, --heapSize);//把堆的第一个元素与堆的最后一个元素交换,然后堆的大小减一 
	
	while(heapSize > 1){//只要堆中的元素大于1 
		heapify(a, 0, heapSize);//因为0位置的数变小了,向下调整大根堆 
		swap(a, 0, --heapSize);//把堆的第一个元素与堆的最后一个元素交换,然后堆的大小减一 
	}
}
void heapinsert(ELEMENT *a, int index)//index位置的数向上调整大根堆 
{
	while(a[index] > a[(index - 1) / 2])//如果下标index的值大于它的父节点 
	{
		swap(a, index, (index - 1) / 2);//下标index的值和它的父节点的值交换 
		index = (index - 1) / 2;//更新index的位置 
	}
}
void heapify(ELEMENT *a, int index, int heapSize)//index位置的数变小了,又想维持大根堆结构,向下调整大根堆 
{
	int left = 2 *index + 1;//下标index的左孩子 
	
	while(left < heapSize)//只要还有孩子 
	{
		int largest = left + 1 < heapSize && a[left + 1] > a[left]? left + 1: left;//如果有右孩子并且右孩子的值大于左孩子的值,那么右孩子就是最大的,否则就是左孩子最大 
		
		largest = a[largest] > a[index]? largest:index;//如果左右孩子中最大的比父节点大,那么最大的还是它,没有父节点大,那么最大的是父节点 
		
		if (largest == index) {//如果最大的是父节点,那么不用调整了,是大根堆了 
			break;
		}
		
		swap(a, largest, index);//父节点与左右孩子中最大的做交换 
		index = largest;//更新index 
		left = index * 2 + 1;//重新计算左孩子的位置 
	}
}
void swap(ELEMENT *a, int i, int j)
{
	ELEMENT t = a[i];
	a[i] = a[j];
	a[j] = t;
}

合并k个有序链表

力扣题目链接

解题思路

  1. 因为链表是按找升序排序的,所有我们需要一个最小堆把每个链表的头节点,放入我们的最小堆。
  2. 然后弹出最小堆的堆顶,那么堆顶肯定是最小的元素,那么让它成为头节点
  3. 然后把堆顶元素的next放入最小堆
  4. 然后一直弹出最小堆中堆顶的元素,再把弹出元素的next节点放入堆,直到堆中没有元素

代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
#define ERROR NULL
typedef struct ListNode* list;
typedef struct ListNode* ELEMENT;

typedef struct heap {
	ELEMENT* data;
	int size;
	int maxSize;
}minHeap, * MinHeap;

MinHeap createMinHeap(int n);
int MinHeapSize(MinHeap h);
bool isMinHeapFull(MinHeap h);
bool addMinHeap(MinHeap h, ELEMENT item);
ELEMENT deleteMinHeap(MinHeap h);
void MinHeapInsert(MinHeap h, int index);
void MinHeapify(MinHeap h, int index);
bool isMinHeapEmpty(MinHeap h);
void swap(MinHeap h, int i, int j);
int MinHeapCompare(ELEMENT a, ELEMENT b);

struct ListNode* mergeKLists(struct ListNode** lists, int listsSize) {
    if (!lists) {//如果没有一条链表那么就返回空
        return NULL;
    }

    list head = NULL;//最终的链表的头节点
    MinHeap h = createMinHeap(listsSize);//创建一个最多能够容纳listsSize的最小堆

    for (int i = 0; i < listsSize; i++) {//先放入每个链表的头节点
        if (lists[i])//只要有头节点
        addMinHeap(h, lists[i]);//放入最小堆
    }

    if (isMinHeapEmpty(h)) {//如果最小堆中没有元素,代表一个节点也没有,返回空
        return NULL;
    }

    head = deleteMinHeap(h);//把最小堆的堆顶元素弹出,让它成为合并链表的头节点
    list cur = head;//cur指向合并链表的尾节点
    if (cur -> next)//如果有下一个节点,再放入head的下一个节点
    addMinHeap(h, cur -> next);

    while(!isMinHeapEmpty(h)) {//只要最小堆中还有元素
        list t = deleteMinHeap(h);//弹出最小堆的堆顶元素

        cur -> next = t;//把新的节点放在链表尾
        cur = cur -> next;//链表尾的位置变了,所有更新链表尾的位置
        if (cur -> next)//只要刚刚弹出的节点还有下一个节点,那么就把它的下一个节点放入最小堆中
        addMinHeap(h, cur -> next);
    }

    cur -> next = NULL;//令链表尾指向空

    return head;//返回合并后的链表的头节点
}
MinHeap createMinHeap(int n)
{
	MinHeap h = (MinHeap)malloc(sizeof(minHeap));

	h -> data = (ELEMENT*)malloc(sizeof(ELEMENT) * n);
	h -> size = 0;
	h -> maxSize = n;

	return h;
}
int MinHeapSize(MinHeap h)
{
	return h->size;
}
bool isMinHeapFull(MinHeap h)
{
	return h->size == h->maxSize;
}
bool addMinHeap(MinHeap h, ELEMENT item)
{
	if (isMinHeapFull(h)) {
		return false;
	}

	h->data[h->size] = item;
	MinHeapInsert(h, h->size);
	h->size++;

    return true;
}
ELEMENT deleteMinHeap(MinHeap h)
{
	if (isMinHeapEmpty(h)) {
		return ERROR;
	}

	ELEMENT ans = h->data[0];
	swap(h, 0, --h->size);
	MinHeapify(h, 0);

	return ans;
}
void MinHeapInsert(MinHeap h, int index)//indexλÖõÄÊýÏòÉϵ÷ÕûС¸ù¶Ñ 
{
	while (MinHeapCompare(h->data[index], h->data[(index - 1) / 2]))//Èç¹ûϱêindexµÄֵСÓÚËüµÄ¸¸½Úµã 
	{
		swap(h, index, (index - 1) / 2);//ϱêindexµÄÖµºÍËüµÄ¸¸½ÚµãµÄÖµ½»»» 
		index = (index - 1) / 2;//¸üÐÂindexµÄλÖà 
	}
}
bool isMinHeapEmpty(MinHeap h)
{
	return !h->size;
}
void swap(MinHeap h, int i, int j)
{
	ELEMENT t = h->data[i];
	h->data[i] = h->data[j];
	h->data[j] = t;
}
void MinHeapify(MinHeap h, int index)
{
	int left = 2 * index + 1;

	while (left < h->size) {
		int largest = left + 1 < h->size && MinHeapCompare(h->data[left + 1], h->data[left]) ? left + 1 : left;

		largest = MinHeapCompare(h->data[index], h->data[largest]) ? index : largest;

		if (largest == index) {
			break;
		}

		swap(h, index, largest);
		index = largest;
		left = 2 * index + 1;
	}
}
int MinHeapCompare(ELEMENT a, ELEMENT b)
{
	return a -> val < b -> val;
}

将数组和减半的最小操作次数

力扣题目链接

解题思路

  1. 题目要求至少的操作次数,那么实质上就是求最少的操作次数,那么很明显是一道贪心的题目,只要我们每次都把数组中最大元素减半,那么必然可以达到最少的操作次数。
  2. 那么怎么快速找到数组最大的元素呢,我们可以用一个有序的结构来存储,那么最大堆是一个不错的选择。

代码实现

#define ERROR -1
typedef double ELEMENT;

typedef struct heap {
	ELEMENT* data;//存放元素的数组 
	int size;//最大堆的元素个数 
	int maxSize;//最大堆最多能够容纳的元素个数 
}maxHeap, * MaxHeap;

MaxHeap createMaxHeap(int n);//创建一个最多能够容纳n个元素的最大堆 
int MaxHeapSize(MaxHeap h);//返回最大堆的元素个数
bool isMaxHeapFull(MaxHeap h);//判断最大堆是不是满了 
bool addMaxHeap(MaxHeap h, ELEMENT item);//往最大堆里面添加元素item
ELEMENT deleteMaxHeap(MaxHeap h);//弹出最大堆的堆顶元素 
void MaxHeapInsert(MaxHeap h, int index);//在最大堆index位置新进来一个元素,所以要向上调整,来让堆仍然是一个最大堆 
void MaxHeapify(MaxHeap h, int index);//在最大堆index位置的元素变小了,所以向下调整堆,来让堆仍然是一个最大堆 
bool isMaxHeapEmpty(MaxHeap h);//判断最大堆空了没有 
void swap(MaxHeap h, int i, int j);//交换最大堆中i和j位置的元素 
int MaxHeapCompare(ELEMENT a, ELEMENT b);//基于某中规则来比较,类似与比较器
void freeMaxHeap(MaxHeap h);//释放最大堆的空间 
ELEMENT peekMaxHeap(MaxHeap h);//返回最大堆的堆顶元素但不弹出

int halveArray(int* nums, int numsSize) {
    MaxHeap h = createMaxHeap(numsSize);//创建一个最大能够容纳数组大小元素的最大堆
    double sum = 0;//数组的累加和
    double sub = 0;//减少的累加和
    int cnt = 0;//统计减少的次数

    for (int i = 0; i < numsSize; i++) {//把数组元素添加到最大堆里面
        addMaxHeap(h, (double)nums[i]);
        sum += nums[i];//算数组的累加和
    }

    while(sub < sum / 2) {//如果减少的累加和大于等于数组的累加和的一半就退出
        double i = deleteMaxHeap(h);//弹出最大堆中的堆顶元素

        i /= 2;//让它减半
        sub += i;//加上减少的数
        addMaxHeap(h, i);//再把减少的数放入最大堆中
        cnt ++;//计数器加一
    }

    freeMaxHeap(h);//释放最大堆的空间

    return cnt;//返回最少减半的次数
}

MaxHeap createMaxHeap(int n)
{
	MaxHeap h = (MaxHeap)malloc(sizeof(maxHeap));//开辟一个最大堆 

	h -> data = (ELEMENT*)malloc(sizeof(ELEMENT) * n);//开辟一个可以存放n个元素的数组 
	h -> size = 0;//初始化最大堆元素是0 
	h -> maxSize = n;//最大堆能存放最大元素的个数是n

	return h;//返回最大堆 
}
int MaxHeapSize(MaxHeap h)
{
	return h->size;//返回最大堆中的元素个数
}
bool isMaxHeapFull(MaxHeap h)
{
	return h->size == h->maxSize;//如果最大堆中的元素个数等于能够容纳的最大元素个数,那么满了
}
bool addMaxHeap(MaxHeap h, ELEMENT item)
{
	if (isMaxHeapFull(h)) {//如果最大堆已经满了,那么就不能添加元素了,返回false 
		return false;
	}

	h->data[h->size] = item;//把item放在最下堆中元素个数的下标中,因为元素个数的下标就是要放的位置 
	MaxHeapInsert(h, h->size);//因为刚刚新进来一个元素,所以想让堆继续保持最大堆,那么就需要向上调整堆 
	h->size++;//让最大堆中的元素个数加一 
	
	return true;//添加成功返回true 
}
ELEMENT deleteMaxHeap(MaxHeap h)
{
	if (isMaxHeapEmpty(h)) {//如果最大堆中没有元素,那么不能进行删除,违法返回一个特定的值,即ERROR
		return ERROR;
	}

	ELEMENT ans = h->data[0];//返回最大堆中的堆顶元素 
	swap(h, 0, --h->size);//把最大堆中的最后一个元素与堆顶元素进行交换,并且让堆中元素减一,那么就访问不到刚刚被删除的元素了。
	MaxHeapify(h, 0);//下标0位置的元素变小了,那么为了继续保持一个最大堆,就要向下调整 

	return ans;//返回最大堆中的堆顶元素 
}
void MaxHeapInsert(MaxHeap h, int index)//index位置的数向上调整大根堆 
{
	while (MaxHeapCompare(h->data[index], h->data[(index - 1) / 2]))//如果下标index的值大于它的父节点 
	{
		swap(h, index, (index - 1) / 2);//下标index的值和它的父节点的值交换 
		index = (index - 1) / 2;//更新index的位置 
	}
}
bool isMaxHeapEmpty(MaxHeap h)
{
	return !h->size;//如果最大堆中的元素是0个,那么就是空,有元素就不是空 
}
void swap(MaxHeap h, int i, int j)
{
	ELEMENT t = h->data[i];
	h->data[i] = h->data[j];
	h->data[j] = t;
}
void MaxHeapify(MaxHeap h, int index)
{
	int left = 2 * index + 1;//计算下标index的左孩子 

	while (left < h->size) {//如果有孩子 
		int largest = left + 1 < h->size && MaxHeapCompare(h->data[left + 1], h->data[left]) ? left + 1 : left;//如果有右孩子并且右孩子大于左孩子,那么孩子中最大的就是右孩子,否则就是左孩子

		largest = MaxHeapCompare(h->data[index], h->data[largest]) ? index : largest;//如果父亲节点比孩子中最大的还要大,那么最大的节点就是父节点,不然最大的节点就是孩子中最大的节点 

		if (largest == index) {//如果最大的节点是父节点,那么不用继续了,已经是最大堆了
			break;
		}

		swap(h, index, largest);//与最大的孩子进行交换 
		index = largest;//更新index下标,因为刚刚交换了
		left = 2 * index + 1;//重新计算下标index的左孩子
	}
}
int MaxHeapCompare(ELEMENT a, ELEMENT b)
{
	return a > b;//直接进行值的比较
}
void freeMaxHeap(MaxHeap h)
{
	free(h -> data);//先释放最大堆存放元素的空间 
	free(h);//再释放最大堆的空间 
}
ELEMENT peekMaxHeap(MaxHeap h)
{
	if (isMaxHeapEmpty(h)) {//最大堆里面没有元素那么,操作违法,返回特定值 
		return ERROR;
	}
	
	ELEMENT ans = deleteMaxHeap(h);//弹出最大堆的堆顶 
	
	addMaxHeap(h, ans);//再放入最大堆,以此来实现代码复用 
	
	return ans;//返回最大堆的堆顶元素 
}

总结

堆的性能非常高效,插入和删除的时间复杂度都是O(logN),因为使用了完全二叉树结构。建立堆的方式有从底建堆,时间复杂度是O(N),而从顶建堆的时间复杂度是O(N * logN)。

posted @   lwj1239  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示