大家好我是小峰,今天我们开始学习二叉树。
首先我们来学习什么是树?
树概念及结构
树的相关概念
我们下面来看看树的结构于概念
树的表示
思路是创建一个链表结构,节点中存储第一个孩子的节点,和下一个兄弟节点,这样不论有多少分支我们都可以表示出来。
接下来我们来学习一个特殊的树。
二叉树概念及结构
特殊的二叉树:
二叉树的存储结构
二叉树的顺序结构及实现
我们通过上面的学习是不是对二叉树有了一定的理解现在我们用顺序结构来实现一个二叉树
我们来看看二叉树顺序结构的性质
在此之前我们先来看看堆的概念和结构
堆的概念及结构
大堆的父亲总是不大于孩子,小堆的父亲总是不小于孩子
现在我们来实现一个堆(这里我们举例实现大堆)
堆的实现
因为每一个堆都是完全二叉树,所以我们用数组来实现一个堆,
这里我们要明白堆的物理结构是数组而堆的逻辑结构是二叉树,要时刻记住。
首先我们还是分装三个文件
堆的实现思路主要是数组,所以我们用顺序表来实现,顺序表的玩法我们都已经很了解了,所以不用啰嗦了我们直接上代码
初始化和销毁
//初始化
void Heapinit(Heap* hp) {assert(hp);hp->capacity = 0;hp->ps = NULL;hp->sz = 0;
}//销毁
void HeapDestory(Heap* hp) {assert(hp);free(hp->ps);hp->ps = NULL;hp->capacity = 0;hp->sz = 0;
}
堆的插入
对于堆的插入我们知道顺序表插入的思路是直接插入,但是我们这里是堆插入我们要满足堆的概念(父亲总是不大于或不小于孩子),所以我们插入后要进行排序,
这里我们来看看逻辑图
我们来看看代码
//堆插入
void Heapush(Heap* hp, CMMlet n) {assert(hp);if (hp->sz == hp->capacity) {int net = hp->ps == NULL ? 4 : hp->capacity * 2;CMMlet* cur = (CMMlet*)realloc(hp->ps, sizeof(CMMlet) * net);if (cur == NULL) {printf("%s", strerror(errno));return;}hp->ps = cur;hp->capacity = net;}hp->ps[hp->sz] = n;hp->sz++;//向上堆排序Adjustup(hp->ps, hp->sz - 1);
}
下面是向上堆排序的代码
//替换函数
tety(CMMlet* p1, CMMlet* p2) {CMMlet n = *p1;*p1 = *p2;*p2 = n;
}
//向上堆排序
//ps待排数据地址,child待排数据下标
void Adjustup(CMMlet* ps, int child) {assert(ps);int n = (child - 1) / 2;while (child > 0) {if (ps[n] < ps[child]) {//替换函数tety(&ps[n], &ps[child]);}else {break;}child = n;n = (child - 1) / 2;}
}
判断堆为空(为空返回true)
这个已经是老玩法了我们直接上代码
//堆的判空
bool HeapEmpty(Heap* hp) {assert(hp);return hp->sz == 0;
}
堆的删除
删除堆是删除堆顶的数据,我们想想顺序表删除表头数据采用的是覆盖删除,如果我们这里也采用覆盖删除,覆盖删除后还要对所有元素重新排序,这种方法可行但不建议,因为所需的时间复杂度太高了,
所以我们这里采用的是先将堆顶元素与最后一个元素调换,删除了最后的元素后再进行向下排序是不是要简单得多啊?这里我们只需要对一个元素排序。
排序的逻辑如下
我们来看代码
//堆的删除
void Heappop(Heap* hp) {assert(hp);assert(!HeapEmpty(hp));//替换tety(&hp->ps[0], &hp->ps[hp->sz - 1]);//删除hp->sz--;//向下堆排序Ajustdown(hp->ps, hp->sz, 0);
}
下面是向下堆排序代码
//向下堆排序
//ps排序数据地址,size排序有效数据个数,mon待排数据下标
void Ajustdown(CMMlet* ps, int size, int mon) {int child = mon * 2 + 1;while (child<size) {//找出最大的孩子if (child+1<size && ps[child] < ps[child + 1]) {++child;}//判断是否符合堆的结构if (ps[mon] < ps[child]) {tety(ps[mon], ps[child]);mon = child;child = mon * 2 + 1;}else {break;}}}
获取堆顶数据
老玩法了我们直接上代码
//获取堆顶数据
CMMlet Heaptop(Heap* hp) {assert(hp);assert(!HeapEmpty(hp));return hp->ps[0];
}
获取堆的有效数据个数
关门,我们直接上代码
//获取堆有效数据个数
int Heapsize(Heap* hp) {assert(hp);return hp->sz;
}
我们来测试一下我们的堆
void csheap() {Heap hp;int add[] = { 12,34,56,78,23,31,54,76,90,1,2, };int a=sizeof(add) / sizeof(add[0]);//初始化Heapinit(&hp);//插入for (int i = 0; i < a; i++) {Heapush(&hp, add[i]);}Heappop(&hp);Heappop(&hp);Heappop(&hp);Heappop(&hp);HeapDestory(&hp);
}int main() {csheap();return 0;
}
执行插入
执行删除
我们调试看现象我们的堆是不是建成了。
堆的应用
堆排序
从我们实现堆的思路中我们可以看出堆的主要应用是排序
总结:升序:建大堆 降序:建小堆
对于排序的思路我们主要用利用堆删除思想来进行排序,我们主要使用向下堆排序法来排序,(向上堆排序法的复杂度太高了)
我们可以用代码实现一下
//排序数据
void addpx(int* add, int mon) {//用向下堆排序建堆for (int i = (mon-1-1)/2; i >=0; --i) {Ajustdown(add, mon, i);}//用堆的删除思路来排序int n = mon;while (n > 0) {int a = add[0];add[0] = add[n-1];add[n-1] = a;//用向下堆排序建堆再次排序n--;Ajustdown(add, n,0);}
}
TOP-K问题
TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。
最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前K个元素来建堆 前k个最大的元素,则建小堆 前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素,将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
我们可以写一个函数来实现
下面函数是在一万个数据中找到五个最大的。
void CreateNDate()
{// 造数据int n = 10000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen error");return;}for (size_t i = 0; i < n; ++i){int x = rand() % 1000000;fprintf(fin, "%d\n", x);}fclose(fin);
}void PrintTopK(int k)
{const char* file = "data.txt";FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen error");return;}int* kminheap = (int*)malloc(sizeof(int) * k);if (kminheap == NULL){perror("malloc error");return;}for (int i = 0; i < k; i++){fscanf(fout, "%d", &kminheap[i]);}// 建小堆for (int i = (k - 1 - 1) / 2; i >= 0; i--){Ajustdown(kminheap, k, i);}int val = 0;while (!feof(fout)){fscanf(fout, "%d", &val);if (val > kminheap[0]){kminheap[0] = val;Ajustdown(kminheap, k, 0);}}for (int i = 0; i < k; i++){printf("%d ", kminheap[i]);}printf("\n");
}int main()
{CreateNDate();PrintTopK(5);return 0;
}
大家注意我们这建的是小堆所以我们的向下堆排序函数是建大堆的所以判断条件要改一下大于小于号。
这是一个测试函数我们写了一万个数据在文件中
我们看是不是找出来了,但由于数据太多了我们也不知道找出来的数据是不是最大的五个,
我们可以验证一下,
我直接更改文件中的数据,既然不知道找出的是不是最大的那我们就手动改五个最大的数不就行了吗
我们再运行试试
是不是找到了?
不知不觉本期内容已经将近尾声了,最后大家看看标准的二叉树大家参拜参拜,愿我们的数据结构都学得炉火纯青
下面是本期全部代码大家可以参考尝试一下
Heap.h
# define _CRT_SECURE_NO_WARNINGS 1
#pragma once# include<stdio.h>
# include<assert.h>
# include<string.h>
# include<stdlib.h>
# include<errno.h>
# include<stdbool.h>
# include<time.h>typedef struct heap Heap;
typedef int CMMlet;struct heap {CMMlet* ps;int sz;//顺序表的元素个数int capacity;//顺序表容量
};//初始化
void Heapinit(Heap* hp);
//销毁
void HeapDestory(Heap* hp);
//堆插入
void Heapush(Heap* hp,CMMlet n);
//向上堆排序
void Adjustup(CMMlet* ps, int child);
//ps待排数据地址,child待排数据下标//堆的判空
bool HeapEmpty(Heap* hp);//堆的删除
void Heappop(Heap* hp);//向下堆排序
void Ajustdown(CMMlet* ps, int size, int mon);
//ps排序数据地址,size排序有效数据个数,mon待排数据下标//获取堆顶数据
CMMlet Heaptop(Heap* hp);//获取堆有效数据个数
int Heapsize(Heap* hp);
Heap.c
# include "heap.h"//初始化
void Heapinit(Heap* hp) {assert(hp);hp->capacity = 0;hp->ps = NULL;hp->sz = 0;
}//销毁
void HeapDestory(Heap* hp) {assert(hp);free(hp->ps);hp->ps = NULL;hp->capacity = 0;hp->sz = 0;
}//替换函数
tety(CMMlet* p1, CMMlet* p2) {CMMlet n = *p1;*p1 = *p2;*p2 = n;
}
//向上堆排序
//ps待排数据地址,child待排数据下标
void Adjustup(CMMlet* ps, int child) {assert(ps);int n = (child - 1) / 2;while (child > 0) {if (ps[n] < ps[child]) {//替换函数tety(&ps[n], &ps[child]);}else {break;}child = n;n = (child - 1) / 2;}
}
//堆插入
void Heapush(Heap* hp, CMMlet n) {assert(hp);if (hp->sz == hp->capacity) {int net = hp->ps == NULL ? 4 : hp->capacity * 2;CMMlet* cur = (CMMlet*)realloc(hp->ps, sizeof(CMMlet) * net);if (cur == NULL) {printf("%s", strerror(errno));return;}hp->ps = cur;hp->capacity = net;}hp->ps[hp->sz] = n;hp->sz++;//向上堆排序Adjustup(hp->ps, hp->sz - 1);
}//堆的判空
bool HeapEmpty(Heap* hp) {assert(hp);return hp->sz == 0;
}//向下堆排序
//ps排序数据地址,size排序有效数据个数,mon待排数据下标
void Ajustdown(CMMlet* ps, int size, int mon) {int child = mon * 2 + 1;while (child<size) {//找出大的孩子if (child+1<size && ps[child] > ps[child + 1]) {++child;}//判断是否符合堆的结构if (ps[mon] > ps[child]) {tety(&ps[mon], &ps[child]);mon = child;child = mon * 2 + 1;}else {break;}}}
//堆的删除
void Heappop(Heap* hp) {assert(hp);assert(!HeapEmpty(hp));//替换tety(&hp->ps[0], &hp->ps[hp->sz - 1]);//删除hp->sz--;//向下堆排序Ajustdown(hp->ps, hp->sz, 0);
}//获取堆顶数据
CMMlet Heaptop(Heap* hp) {assert(hp);assert(!HeapEmpty(hp));return hp->ps[0];
}//获取堆有效数据个数
int Heapsize(Heap* hp) {assert(hp);return hp->sz;
}
test.c
# include"heap.h"排序数据
//void addpx(int* add, int mon) {
// //用向下堆排序建堆
// for (int i = (mon-1-1)/2; i >=0; --i) {
// Ajustdown(add, mon, i);
// }
// //用堆的删除思路来排序
// int n = mon;
// while (n > 0) {
// int a = add[0];
// add[0] = add[n-1];
// add[n-1] = a;
// //用向下堆排序建堆再次排序
// n--;
// Ajustdown(add, n,0);
// }
//}
//void csheap() {
// Heap hp;
// int add[] = { 12,34,56,78,23,31,54,76,90,1,2, };
// int a=sizeof(add) / sizeof(add[0]);
// addpx(add, a);
//}
//
//int main() {
// csheap();
//
//
//
// return 0;
//}void CreateNDate()
{// 造数据int n = 10000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen error");return;}for (size_t i = 0; i < n; ++i){int x = rand() % 1000000;fprintf(fin, "%d\n", x);}fclose(fin);
}void PrintTopK(int k)
{const char* file = "data.txt";FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen error");return;}int* kminheap = (int*)malloc(sizeof(int) * k);if (kminheap == NULL){perror("malloc error");return;}for (int i = 0; i < k; i++){fscanf(fout, "%d", &kminheap[i]);}// 建小堆for (int i = (k - 1 - 1) / 2; i >= 0; i--){Ajustdown(kminheap, k, i);}int val = 0;while (!feof(fout)){fscanf(fout, "%d", &val);if (val > kminheap[0]){kminheap[0] = val;Ajustdown(kminheap, k, 0);}}for (int i = 0; i < k; i++){printf("%d ", kminheap[i]);}printf("\n");
}int main()
{//CreateNDate();PrintTopK(5);return 0;
}
以上就是全部内容了,如果有错误或者不足的地方欢迎大家给予建议。