堆的概念
:::block-1
- 堆是一种完全二叉树,它的所有元素都按照完全二叉树的顺序存储方式存储,一般使用数组来实现。
:::
堆的分类
:::block-1
- 小堆
小堆是指满足k[i]<=k[2i+1] && k[i]<=k[2i+2]的堆,通俗来讲就是其父节点一定要小于任何一个子节点。 - 大堆
大堆是指满足k[i]>=k[2i+1] && k[i]>=k[2i+2]的堆,通俗来讲就是其父节点一定要大于任何一个子节点。
:::
通过子节点(父节点)下标寻找父节点(子节点)下标
- 子节点找父节点
父 = (子-1)/2 - 父节点找子节点
子 = (父*2)+1
堆的实现
:::block-1
- 堆的实现需要依靠两种算法,一种是向下调整算法、另一种是向上调整算法,这两种算法具体的应用在下文给出。
堆的结构
- 和顺序表的结构有点像,因为我们使用顺序表去实现堆。
typedef int phpDataType;typedef struct Heap
{phpDataType* a;int size;int capacity;
}Heap;
向下调整算法
- 过程为:给定父亲下标,向下寻找子节点,满足条件则交换。
void AdjustDwon(int* a, int n, int parent)
{assert(a);int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child] > a[child + 1]){child++;}if (a[parent] > a[child]){Swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}else{break;}}
}
向上调整算法
- 过程为:给定孩子下标,向上寻找父节点,满足条件则交换。
void AdjustUp(Heap* php,int child)//堆,要调整的位置
{assert(php);int parent = (child - 1) / 2;while (child > 0)//子节点不等于零代表有父节点,可继续调整{if (php->a[child] < php->a[parent])//给定条件{Swap(&(php->a[child]), &(php->a[parent]));//交换子父节点}else{break;//如果不满足条件,则跳出进入下一次调整}child = parent;parent = (child - 1) / 2;}
}
堆的创建
- 堆的创建有两种方式,一种是通过向下调整建堆(倒着建堆),另外一种是向上调整建堆(模拟插入的过程)
向下调整建堆
- 从数组最后开始建堆
//(n - 1 - 1)/2的含义是((n-1)-1)/2,n-1是数组最后一个元素的下标,减一后整体处二是为了找他的父亲节点,因为叶子节点没有必要去向下调整。
for (int i = (n - 1 - 1)/2; i >= 0; i--){AdjustDwon(a, n, i);}
向上调整建堆
- 这实际上是一个模拟插入的过程,将数组第一个元素看做是堆,将后面的元素看做要插入的元素,插入一个便向上调整一次
for (int i = 1; i < n; i++){AdjustUp(a, i);}
堆的初始化
- php->capacity = 0; 将堆的容量设置为0,表示当前堆中没有元素。
- php->size = 0; 将堆的大小设置为0,表示当前堆中没有元素。
- php->a = NULL; 将指向堆元素数组的指针a设置为NULL,表示当前堆中没有任何元素。
- 通过调用这个函数,可以将一个Heap对象初始化为空的状态,以便后续进行堆操作。
void InitHeap(Heap* php)
{php->capacity = 0;php->size = 0;php->a = NULL;
}
堆的插入的实现
- assert(php); 使用断言确保传入的指针不为空。
- phpDataType NewCapacity = php->capacity == 0 ? 4 : 2 * php->capacity; 根据堆的当前容量来计算新的容量,如果当前容量为0,则将新容量设置为4,否则设置为当前容量的两倍。
- if (php->capacity == php->size) 如果堆的容量等于当前大小,说明堆已满,需要进行扩容操作。
- phpDataType* tmp = (phpDataType*)realloc(php->a,NewCapacity*sizeof(phpDataType)); 使用realloc函数重新分配堆的元素数组的内存空间,将容量调整为新容量。
- assert(tmp); 使用断言确保内存分配成功。
- php->capacity = NewCapacity; 更新堆的容量为新容量。
- php->a = tmp; 更新堆的元素数组指针为新的内存空间。
- php->a[php->size] = x; 将需要插入的数据存放在堆的末尾。
- php->size++; 堆的大小增加1,表示插入了一个新元素。
- AdjustUp(php,php->size-1); 调用AdjustUp函数对插入的新元素进行向上调整,以满足堆的条件。
- 通过调用这个函数,可以将数据插入到堆中,并确保堆的性质得到维护。
void HeapPush(Heap* php, phpDataType x)//堆,需要插入的数据
{assert(php);phpDataType NewCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;if (php->capacity == php->size){phpDataType* tmp = (phpDataType*)realloc(php->a,NewCapacity*sizeof(phpDataType));assert(tmp);php->capacity = NewCapacity;php->a = tmp;}php->a[php->size] = x;php->size++;AdjustUp(php,php->size-1);//要调整的对,需要向上调整的新的位置
}
堆的销毁
- assert(php); 使用断言确保传入的指针不为空。
- php->size = 0; 将堆的大小设置为0,表示当前堆中没有元素。
- php->capacity = 0; 将堆的容量设置为0,表示当前堆中没有元素。
- free(php->a); 调用free函数释放堆元素数组的内存空间。
- 通过调用这个函数,可以销毁堆并释放相关的内存空间。
void HeapDestory(Heap* php)
{assert(php);php->size = 0;php->capacity = 0;free(php->a);
}
堆的删除(删除堆顶元素)
- assert(php); 使用断言确保传入的指针不为空。
- assert(php->size); 使用断言确保堆的大小大于0,即堆中至少有一个元素。
- int top = 0; 将top变量设置为0,表示堆顶的位置。
- int end = php->size - 1; 将end变量设置为堆的最后一个元素的位置。
- Swap(&(php->a[top]), &(php->a[end])); 调用Swap函数交换堆顶元素和最后一个元素的位置,将堆顶元素移到堆的末尾。
- php->size–; 堆的大小减少1,表示弹出了一个元素。
- AdjustDown(php); 调用AdjustDown函数对堆进行向下调整,以满足堆的条件。
通过调用这个函数,可以弹出堆顶元素并保持堆的性质。
void HeapPop(Heap* php)
{assert(php);assert(php->size);int top = 0;int end = php->size - 1;Swap(&(php->a[top]), &(php->a[end]));php->size--;AdjustDown(php);
}
取堆顶的数据
- assert(php); 使用断言确保传入的指针不为空。
- int top = 0; 将top变量设置为0,表示堆顶的位置。
- return php->a[top]; 返回堆中位置为top的元素的值。
- 通过调用这个函数,可以获取堆顶元素的值而不对堆进行修改。
phpDataType HeapTop(Heap* php)
{assert(php);int top = 0;return php->a[top];
}
堆的数据个数
- assert(php); 使用断言确保传入的指针不为空。
- return php->size; 返回堆的大小,即堆中元素的个数。
- 通过调用这个函数,可以获取堆的大小而不对堆进行修改。
int HeapSize(Heap* php)
{assert(php);return php->size;
}
堆的判空
- assert(php); 使用断言确保传入的指针不为空。
- return php->size == 0; 返回一个表达式的结果,该表达式判断堆的大小是否为0。如果堆的大小为0,则返回1代表堆为空;否则,返回0代表堆非空。
- 通过调用这个函数,可以判断堆是否为空。若返回值为1,则表示堆为空;若返回值为0,则表示堆非空。
int HeapEmpty(Heap* php)//返回1代表该堆为空,返回零代表非空
{assert(php);return php->size == 0;
}
堆排序(升序建大堆,降序建小堆)
- 向下调整建堆后,将堆顶元素不断换向堆底
- assert(a); 使用断言确保传入的数组指针不为空。
- 首先进行建堆操作。通过循环从堆的倒数第二层开始,依次对每个非叶子节点调用AdjustDown函数,将数组a调整为一个合法的堆。
- 接下来是排序阶段。使用一个while循环,不断地将堆顶元素(数组首元素a[0])与当前待排序范围内的最后一个元素交换,并将待排序范围缩小,再对堆顶元素进行一次AdjustDown操作,以满足堆的性质。
- 循环执行上述步骤,直到待排序范围的大小减为0,即完成了堆排序。
- 通过调用这个函数,可以对指定的整型数组进行堆排序,使其按照升序排列。
void HeapSort(int* a, int n)
{assert(a);//建堆for (int i = (n - 1 - 1)/2; i >= 0; i--){AdjustDwon(a, n, i);}//排序while (n){Swap(&a[0], &a[n - 1]);n--;AdjustDwon(a, n, 0);}
}
top-k问题
- 首先,定义一个字符串变量file表示文件名,并使用 fopen 函数以只读方式打开文件。使用条件判断语句检查文件是否成功打开,如果打开失败,则输出错误信息并返回。
- 动态分配一个大小为k的整型数组a,用于存储前k个最大数。
- 使用循环从文件中读取k个整数并依次存入数组a中。
- 接下来进行建堆操作。通过循环从堆的倒数第二层开始,依次对每个非叶子节点调用AdjustDown函数,将数组a调整为一个合法的堆。
- 然后,使用一个while循环,在文件没有读取完的情况下不断读取下一个整数,并与数组a的堆顶元素比较。
- 如果新读取的整数大于数组a的堆顶元素,则将该整数替换堆顶元素,并调用AdjustDown函数重新调整堆。
- 最后,使用循环遍历数组a,并逐个打印出前k个最大数。
void PrintTopK(int k)
{const char* file = "data.txt";FILE* fin = fopen(file, "r");if (fin == NULL){perror("fopen error");return;}int* a = (int*)malloc(k * sizeof(int));for (int i = 0; i < k; i++){fscanf(fin, "%d", &a[i]);}//建堆for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(a,k,i);}int val = 0;while (!feof(fin)){fscanf(fin, "%d", &val);if (val > a[0]){a[0] = val;AdjustDown(a, k, 0);}}for (int i = 0; i < k; i++){printf("%d\n", a[i]);}
}
:::