目录
4.在一个堆中,下标为 i(i > 0) 的结点的左右孩子结点及父结点的下标分别是( )
后记:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!
题目:
【单选题】
1.下列关于堆的叙述错误的是( )
A.堆是一种完全二叉树
B.堆通常使用顺序表存储
C.小堆指的是左右孩子结点都比根结点小的堆
D.堆的删除是将尾部结点放到队顶后执行向下调整算法
2.下列关键字序列中,序列( )是堆。
A.{16,72,31,23,94,53}
B.{94,23,31,72,16,53}
C.{16,53,23,94,31,72}
D.{16,23,53,31,94,72}
3.下列关于向下调整算法的说法正确的是( )
A.构建堆的时候要对每个结点都执行一次
B.删除操作时要执行一次
C.插入操作时要执行一次
D.以上说法都不正确
4.在一个堆中,下标为 i(i > 0) 的结点的左右孩子结点及父结点的下标分别是( )
A.2 i、2 i + 1、i /2
B.2i、2i + 1、(i - 1)/2
C.2i + 1、2i + 2、(i - 1)/2
D.2i + 1、2i + 2、i/2-1
5.将一个顺序表整理成堆的时间复杂度为( )
A.O(nlogn)
B.O(logn)
C.O(1)
D.O(n)
【编程题】
6.堆的实现
7.堆排序实现
解析:
1.答案:C
解析:大堆小堆所表示的意思是堆顶元素为最大值/最小值。并且每一个子结构也都满足这样的性质。
2.答案:D
解析:选项D是一个小根堆。
3.答案:B
解析:A: 建堆时,每一个非叶子节点都要执行一次向下调整算法。
B: 删除元素时,首先交换堆顶元素的堆尾元素,删除堆尾元素,然后执行向下调整算法。
C: 插入操作需要执行向上调整算法。
4.
5.答案:D
解析:建立堆的顺序是bottom-top的。
正确的证明方法应当如下:
A.具有n个元素的平衡二叉树,树高为㏒n,我们设这个变量为h。
B.最下层非叶节点的元素,只需做一次线性运算便可以确定大根,而这一层具有2^(h-1)个元素,我们假定O(1)=1,那么这一层元素所需时间为2^(h-1) × 1。
C.由于是bottom-top建立堆,因此在调整上层元素的时候,并不需要同下层所有元素做比较,只需要同其中之一分支作比较,而作比较次数则是树的高度减去当前节点的高度。因此,第x层元素的计算量为2^(x) × (h-x)。
D.又以上通项公式可得知,构造树高为h的二叉堆的精确时间复杂度为:
S = 2^(h-1) × 1 + 2^(h-2) × 2 + …… +1 × (h-1) ①
E.通过观察第四步得出的公式可知,该求和公式为等差数列和等比数列的乘积,因此用错位相减法求解,给公式左右两侧同时乘以2,可知:
2S = 2^h × 1 + 2^(h-1) × 2+ …… +2 × (h-1) ②
用②减去①可知: S =2^h × 1 - h +1 ③
将h = ㏒n 带入③,得出如下结论:
S = n - ㏒n +1 = O(n)
6.
typedef int HPDataType; typedef struct Heap { HPDataType* _a; int _size; int _capacity; }Heap; // 堆的构建 void HeapCreate(Heap* hp, HPDataType* a, int n); // 堆的销毁 void HeapDestory(Heap* hp); // 堆的插入 void HeapPush(Heap* hp, HPDataType x); // 堆的删除 void HeapPop(Heap* hp); // 取堆顶的数据 HPDataType HeapTop(Heap* hp); // 堆的数据个数 int HeapSize(Heap* hp); // 堆的判空 int HeapEmpty(Heap* hp); // TopK问题:找出N个数里面最大/最小的前K个问题。 // 比如:未央区排名前10的泡馍,西安交通大学王者荣耀排名前10的韩信,全国排名前10的李白。等等问题都是Topk问题, // 需要注意: // 找最大的前K个,建立K个数的小堆 // 找最小的前K个,建立K个数的大堆 void PrintTopK(int* a, int n, int k); void TestTopk();
#include "Heap.h" void Swap(HPDataType* x1, HPDataType* x2) { HPDataType x = *x1; *x1 = *x2; *x2 = x; } void AdjustDown(HPDataType* a, int n, int root) { int parent = root; 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; } } } void AdjustUp(HPDataType* a, int n, int child) { int parent; assert(a); parent = (child-1)/2; //while (parent >= 0) while (child > 0) { //如果孩子大于父亲,进行交换 if (a[child] > a[parent]) { Swap(&a[parent], &a[child]); child = parent; parent = (child-1)/2; } else { break; } } } void HeapInit(Heap* hp, HPDataType* a, int n) { int i; assert(hp && a); hp->_a = (HPDataType*)malloc(sizeof(HPDataType)*n); hp->_size = n; hp->_capacity = n; for (i = 0; i < n; ++i) { hp->_a[i] = a[i]; } // 建堆: 从最后一个非叶子节点开始进行调整 // 最后一个非叶子节点,按照规则: (最后一个位置索引 - 1) / 2 // 最后一个位置索引: n - 1 // 故最后一个非叶子节点位置: (n - 2) / 2 for(i = (n-2)/2; i >= 0; --i) { AdjustDown(hp->_a, hp->_size, i); } } void HeapDestory(Heap* hp) { assert(hp); free(hp->_a); hp->_a = NULL; hp->_size = hp->_capacity = 0; } void HeapPush(Heap* hp, HPDataType x) { assert(hp); //检查容量 if (hp->_size == hp->_capacity) { hp->_capacity *= 2; hp->_a = (HPDataType*)realloc(hp->_a, sizeof(HPDataType)*hp->_capacity); } //尾插 hp->_a[hp->_size] = x; hp->_size++; //向上调整 AdjustUp(hp->_a, hp->_size, hp->_size-1); } void HeapPop(Heap* hp) { assert(hp); //交换 Swap(&hp->_a[0], &hp->_a[hp->_size-1]); hp->_size--; //向下调整 AdjustDown(hp->_a, hp->_size, 0); } HPDataType HeapTop(Heap* hp) { assert(hp); return hp->_a[0]; } int HeapSize(Heap* hp) { return hp->_size; } int HeapEmpty(Heap* hp) { return hp->_size == 0 ? 0 : 1; } void HeapPrint(Heap* hp) { int i; for (i = 0; i < hp->_size; ++i) { printf("%d ", hp->_a[i]); } printf("\n"); } //TopK问题: 找出N个数里面最大/最小的前K个问题。 //这里实现两个版本: //1. 找最大的K个元素 //假设堆为小堆 void PrintTopK(int* a, int n, int k) { Heap hp; //建立含有K个元素的堆 HeapInit(&hp, a, k); for (size_t i = k; i < n; ++i) // N { //每次和堆顶元素比较,大于堆顶元素,则删除堆顶元素,插入新的元素 if (a[i] > HeapTop(&hp)) // LogK { HeapPop(&hp); HeapPush(&hp, a[i]); } } for(int i = 0; i < k; ++i){ printf("%d ",HeapTop(&hp)); HeapPop(&hp); } } //2. 找最小的K个元素 //假设堆为大堆 void PrintTopK(int* a, int n, int k) { Heap hp; //建立含有K个元素的堆 HeapInit(&hp, a, k); for (size_t i = k; i < n; ++i) // N { //每次和堆顶元素比较,小于堆顶元素,则删除堆顶元素,插入新的元素 if (a[i] < HeapTop(&hp)) // LogK { HeapPop(&hp); HeapPush(&hp, a[i]); } } for(int i = 0; i < k; ++i){ printf("%d ",HeapTop(&hp)); HeapPop(&hp); } } void TestTopk() { int n = 10000; int* a = (int*)malloc(sizeof(int)*n); srand(time(0)); //随机生成10000个数存入数组,保证元素都小于1000000 for (size_t i = 0; i < n; ++i) { a[i] = rand() % 1000000; } //确定10个最大的数 a[5] = 1000000 + 1; a[1231] = 1000000 + 2; a[531] = 1000000 + 3; a[5121] = 1000000 + 4; a[115] = 1000000 + 5; a[2335] = 1000000 + 6; a[9999] = 1000000 + 7; a[76] = 1000000 + 8; a[423] = 1000000 + 9; a[3144] = 1000000 + 10; PrintTopK(int* a, n, 10); }
7.
// 对数组进行堆排序 void HeapSort(int* a, int n);
// 排升序 void HeapSort(int* a, int n) { // 建大堆 -》 for (int i = (n - 1 - 1) / 2; i >= 0; --i) { AdjustDown(a, n, i); } int end = n - 1; while (end > 0) { Swap(&a[0], &a[end]); // 选出次大的 AdjustDown(a, end, 0); --end; } }