赞
踩
希望这篇文章对你有益,如果你发现哪里有问题,希望您能不吝赐教。
例如:堆的逻辑结构是完全二叉树,看后面的知识需要你有一点点二叉树的概念,你需要知道父子结点,二叉树的结构,以及顺序表的基本知识!
数据结构的堆物理结构是数组,逻辑结构是完全二叉树(这里可以联想链表的物理、逻辑结构的不同),这里引入一个问题就是:如何让数组变成二叉树,也就是说一个数据如何与另外两个数据产生联系?带着这个问题,我们往下看。
给一个数组{9,7,2,6,3,5,1,4,8},他的逻辑结构:
0->1、0->2、1->3、1->4 反过来列举我们可以通过下标找到数据查找关系
由这两个不同角度的重要结论,我们可以由此实现物理->逻辑的联系,下面来实现建堆!
堆分为小堆和大堆,小堆是所有的双亲结点要小于自己的字结点,大堆就是双亲结点要大于自己的子节点。建堆就是将堆建成小堆或者大堆。
下面上代码教你如何建堆~
1.堆的基本框架(顺序表)
- typedef int HeapDataType;
- typedef struct Heap
- {
- HeapDataType* a;
- int size;
- int capacity;
- }Heap;
2.堆的初始化
- void HeapInit(Heap* php)
- {
- php->a = (HeapDataType*)malloc(sizeof(HeapDataType) * 8);
- php->capacity = 8;
- php->size = 0;
- }
Way1:向上调整法
- //向上排序法(孩子找父亲) 时间复杂度0(N*logN)
- void AdjustUp(HeapDataType* a, int child)
- {
- //1.找父亲
- int parent = (child - 1) / 2;
- while (child > 0)
- {
- //2.比较父子结点
- if (a[child] > a[parent])
- {
- //交换
- swap(&a[child], &a[parent]);
- //迭代
- child = parent;
- parent = (child - 1) / 2;
- }
- else {
- break;
- }
- }
- }
- //建堆
- void HeapCreate(Heap* php, HeapDataType* a, int size)
- {
- for (int i = 0;i < size;i++)
- {
- HeapPush(php, a[i]);
- }
- }
方法:通过子节点找到父结点,比较父与子结点大小,子节点大于父结点那么就交换,否则不交换。不交换说明这对父子结点已满足大堆条件,不需要动它们;交换则迭代执行下一次比较!
计算时间复杂度:
Way2:向下调整法(大堆为例)
思路:从第一个非叶结点开始,从子结点中找出最大的结点,比较父子结点的大小,如果子结点大于父节点则交换,然后迭代,否则说明已满足大堆条件,直接结束调整。
- //利用向下调整法建大堆 时间复杂度0(N)
- int arr[] = { 9,7,2,6,3,5,1,4,8 };
- //这里i起始应该是第一个非叶子结点 最后一个结点时n-1,parent=(n-1-1)/2
- for (int i = (n-2)/2;i>=0;i--)
- {
- AdjustDown(a, n, i);
- }
- void AdjustDown(HeapDataType* a, int size,int parent)
- {
- 1.父亲找孩子
- int minchild = parent * 2 + 1;
- while (minchild < size )
- {
- //2.找出“长子”
- if (minchild + 1 < size && a[minchild+1] > a[minchild])
- {
- minchild = minchild + 1;
- }
- //3.比较父子
- if (a[minchild] > a[parent])
- {
- //子节点>父节点,交换
- swap(&a[minchild], &a[parent]);
- //迭代
- parent = minchild;
- minchild = parent * 2 + 1;
- }
- else {
- break;
- }
- }
- }
计算时间复杂度:
理解了建堆的方法,我们就可以考虑堆的应用->堆排序
我们直到大堆,小堆的特性以后可以隐约地感觉这种结构可以发挥数据的选择,排序就是一种数据有序的要求,这里强调的是大堆小堆并未完全实现数组有序,由上面的例子可以看出大小堆并非有序,但堆顶元素具有Max,Min的性质!
现在问一个问题:排升序是建大堆,还是小堆?排降序呢?停下来自己思考一下!
很多人会不暇思索地回答:“小堆!”,原因是小堆堆顶正好是最小的!现上图来帮你分析一下为啥小堆排升序是效率很低的。
现在上代码~
- void HeapSort(int*a,int n)
- {
- //利用向下调整法建大堆 时间复杂度0(N)
- for (int i = (n-2)/2;i>=0;i--)
- {
- AdjustDown(a, n, i);
- }
- //排序 时间复杂度0(N*logN)
- for (int i = 1;i < n;i++)
- {
- swap(&a[0], &a[n-i]);
- //再向下建大堆
- AdjustDown(a, n-i, 0);
- }
- }
- void TestHeapSort()
- {
- int arr[] = { 9,7,2,6,3,5,1,4,8 };
- HeapSort(arr, sizeof(arr) / sizeof(arr[0]));
- for (int i = 0;i < sizeof(arr) / sizeof(arr[0]);i++)
- {
- printf("%d ", arr[i]);
- }
- }
TopK问题:找出N个数里面最大/最小的前K个问题。 比如:未央区排名前10的泡馍,西安交通大学王者荣耀排名前10的韩信,全国排名前10的李白。等等问题都是Topk问题。
方法1:直接堆排序 时间复杂度O(N*logN)
方法二:建大堆,取堆顶,与最后一个值交换然后继续建堆 时间复杂度O(N+K*logN) 该方法适用于N较小时,当N很大该方法效率不如方法三
方法三:对前K个数建小堆,然后遍历后N-K个数,如果该数比小堆堆顶大就替换,替换完之后重新建小堆,重复直到遍历结束。 时间复杂度O(K+(N-K)*logK) 适用于N很大的情况下
现上法二、方法三代码~
方法二:
- void PrintTopK1(int* a, int n, int k)
- {
- assert(k < n&& k>0);
- //建大堆
- for (int i = (n - 2) / 2;i >= 0;i--)
- {
- AdjustDown(a, n, i);
- }
- //取TopK
- int i = 1;
- while (i<=k)
- {
- printf("%d ", a[0]);
- swap(&a[0], &a[n - i]);
- AdjustDown(a, n - i, 0);
- i++;
- }
- }
- void TestTopk()
- {
- int arr[] = { 9,7,2,6,3,5,1,4,8 };
- PrintTopK1(arr, sizeof(arr) / sizeof(int), 3);//建大堆 N较小情况下选择
- }
方法三:
- //随机生成N个随机数 控制随机数数值范围,然后把文件中几个值手动修改中成几个>随机数最大值的值
- void CreateData(int N)
- {
- srand((unsigned int)time(NULL));
- FILE* fout = fopen("Heap.txt", "w");
- if (fout == NULL)
- {
- perror("fopen failed");
- exit(-1);
- }
- for (int i = 0;i < N;i++)
- {
- fprintf(fout, "%d ", rand()%1000);
- }
- fclose(fout);
- }
- void PrintTopK2(const char*filename, int N, int k)
- {
- FILE* fout = fopen(filename, "r");
- //读取k个数据 空间复杂度0(k)
- int* a = (int*)malloc(sizeof(int) * k);
- for (int i = 0;i < k;i++)
- {
- fscanf(fout, "%d", &a[i]);
- }
- //向下调整法建立小堆 时间复杂度0(k)
- for (int i = (k - 2) / 2;i >= 0;i--)
- {
- AdjustDown(a, k, i);
- }
- //继续读取,寻找大值,然后重新建堆 时间复杂度(N-k)*logk
- int val;
- while (fscanf(fout, "%d", &val) != EOF)
- {
- if (val > a[0]) {
- a[0] = val;
- AdjustDown(a, k, 0);
- }
- }
- for (int i = 0;i < k;i++)
- {
- printf("%d ", a[i]);
- }
- free(a);
- fclose(fout);
- }
-
- void TestTopk()
- {
-
- int N = 1000;
- CreateData(N);
- PrintTopK2("Heap.txt", N, 10);//建小堆 适合N数据比较大的情况
- }
堆是数据结构中比较重要的一部分,它可以应用到排序的算法中。对于堆,我们一定要掌握其建堆的核心思想,这是重中之重!最后,希望这篇文章对你有益,如果你发现哪里有问题,希望您能不吝赐教。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。