二叉树(详解)中

简介: 二叉树(详解)

3.3.6堆的代码实现


定义类型和结构体


typedef int Datatype;
typedef struct Heap
{
  Datatype* a;//创建数组存储元素
  Datatype size;//计算元素个数
  Datatype capacity;//堆的容量
}Heap;


初始化堆


//初始化堆
void Heapinit(Heap* hp);
void Heapinit(Heap* hp)
{
  assert(hp);
  hp->a = NULL;
  hp->size = hp->capacity = 0;
}


246a3f63fb54250b5e571cf1fe190133_db923d2abba34646a4ccbde89a162eff.png


销毁堆


//销毁堆
void Heapdestory(Heap* hp);
void Heapdestory(Heap* hp)
{
  assert(hp);
  free(hp->a);
  hp->a = NULL;
  hp->size = hp->capacity = 0;
}


插入数据


由上面堆的插入可知,数据从数组的末尾插入,然后进行向上调整,直到构建新的堆


由于交换元素在堆的其他功能中也会使用,为了方便就将其独立为函数


交换元素


void Swap(Datatype* p1, Datatype* p2)
{
  int tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}


//插入数据
void Heappush(Heap* hp, Datatype x);
void Adjustup(Datatype* a, Datatype child)
{
  assert(a);
  int parent = (child - 1) / 2;
  while (child > 0)
  {
  if (a[parent] > a[child])
  {
    Swap(&a[parent], &a[child]);
    child = parent;
    parent = (child - 1) / 2;
  }
  else
  {
    break;
  }
  }
}
void Heappush(Heap* hp, Datatype x)
{
  assert(hp);
  //判断容量
  if (hp->size == hp->capacity)
  {
  int newcapacity = hp->capacity == 0 ? 4 : 2 * hp->capacity;
  Datatype* tmp = (Datatype*)realloc(hp->a, newcapacity * sizeof(Datatype));
  if (tmp == NULL)
  {
    perror("realloc fail");
    exit(-1);
  }
  hp->capacity = newcapacity;
  hp->a = tmp;
  }
  hp->a[hp->size] = x;
  hp->size++;
  //向上调整
  Adjustup(hp->a, hp->size - 1);
}


将数据 5,4,8,2,9,10,7 插入堆中,经过向上调整最终变成小根堆


6ad6933d9b99de8805c211175984dc43_2b9467066fae46ac80b6d598f66b7d12.png


小根堆


45340ac498a2a461addf7f1752d5d6a7_50e655af2c764449a946d64f2a750f9d.png


打印堆


//打印堆
void Heapprint(Heap* hp);
void Heapprint(Heap* hp)
{
  assert(hp);
  assert(!Heapempty(hp));
  int i = 0;
  for (i = 0; i < hp->size; i++)
  {
  printf("%d ", hp->a[i]);
  }
  printf("\n");
}


53b81ee21419bbcdfe462d1eaf913f0e_cb0b7550ac344b4682b9419a337563dd.png


读取堆顶数据


//读取堆顶数据
Datatype Heaptop(Heap* hp);
//读取堆顶数据
Datatype Heaptop(Heap* hp)
{
  assert(hp);
  assert(!Heapempty(hp));
  return hp->a[0];
}


31e8abd9a3253a2d2dab17018deeb98d_ddb1217515304d89b945063a596501d4.png


删除堆顶数据


//删除堆顶数据
void Heappop(Heap* hp);
void Adjustdown(Datatype* a, int n, int parent)
{
  assert(a);
  int minchild = parent * 2 + 1;
  while (minchild < n)
  {
  if (minchild + 1 < n && a[minchild+1] < a[minchild])
  {
    minchild++;
  }
  if (a[minchild] < a[parent])
  {
    Swap(&a[minchild], &a[parent]);
    parent = minchild;
    minchild = parent * 2 + 1;
  }
  else
  {
    break;
  }
  }
}
void Heappop(Heap* hp)
{
  assert(hp);
  assert(!Heapempty(hp));
  Swap(&hp->a[0], &hp->a[hp->size - 1]);
  hp->size--;
  //向下调整
  Adjustdown(hp->a, hp->size, 0);
}

78954a48d108775dd24f4cf2257d9bdc_7b3114e0615f4445bba9d3cc7237c4cc.png

58d4a4b19b159681376bf91ae974a431_edef4e18e5034c2ea5bba4ea0a13082c.png


判断堆是否为空


//判断堆是否为空
bool Heapempty(Heap* hp);
bool Heapempty(Heap* hp)
{
  assert(hp);
  return hp->size == 0;
}


计算堆中的元素的个数


//计算堆中的元素的个数
int Heapsize(Heap* hp);
int Heapsize(Heap* hp)
{
  assert(hp);
  return hp->size;
}


以上是通过将数组中的值传到堆中,再进行堆的调整,最后变为大根堆或小根堆


那么可不可以直接在数组中建堆,再进行调整,最终变为大根堆或小根堆呢?


答案是:当然可以


利用向上调整算法或者向下调整算法直接再数组中建堆,最终变为大根堆或者小根堆


代码实现如下


向上调整建堆


void HeapCreat(int* a, int n)
{
  int i = 0;
  //向上建堆
  for (i = 1; i < n; i++)
  {
  Adjustup(a, i);
  }
}
int main()
{
  Heap hp;
  int arr[] = { 5,4,8,2,9,10,7 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  HeapCreat(arr, sz);
  return 0;
}


5913ebfbf20172150c3cc7b4fcc3b03a_9e90e9d8fa804162bf21a5aa9915afae.png


向上建堆时间复杂度计算


d478f645ad4f23d43f8f8f42c163227f_ffde656d3c3241c0a8b296432d4883ab.png


调整次数=每层节点个数*每层向下调整的次数(最坏情况下,全部调整)


T(N)=2^1* 1 + 2^2 * 2+ 2^3* 3+…+2^(h-1) *(h-1)


2^h -1 = N h=log(N+1)


估算T(N) = N*log(N)


向下调整


根据上面介绍的思路,代码实现如下


void HeapCreat(int* a, int n)
{
  int i = 0;
  for (i = (n - 1 - 1) / 2; i >= 0; i++)
  {
  Adjustdown(a, i);
  }
}
int main()
{
  Heap hp;
  int arr[] = { 5,4,8,2,9,10,7 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  HeapCreat(arr, sz);
  return 0;
}


7c2c1e4bd39102929aa1be40e01f4dbd_0735eb5f3c2a4dd4b714f1124090211a.png


3.4堆的应用


3.4.1堆排序


堆排序便是利用堆的特点进行排序,分为两个步骤


1.建堆


升序:建大堆
  降序:建小堆


2.通过堆删除的思路进行排序


升序


建大堆

把最后一个节点和第一个节点交换,不把最后一个节点看作堆中。开始开始向下调整,选出次大的元素,依次类推

代码实现


void HeapCreat(int* a, int n)
{
  int i = 0;
  //向下建堆  建大堆
  for (i = (n - 1 - 1) / 2; i >= 0; --i)
  {
  Adjustdown(a, n, i);
  }
  i = 1;
  while (i < n)
  {
     交换第一个和最后一个节点
  Swap(&a[0], &a[n - i]);
  重新向下调整
  Adjustdown(a, n-i, 0);
  ++i;
  }
}
int main()
{
  int arr[] = { 5,4,8,2,9,10,7 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  HeapCreat(arr, sz);
  int i = 0;
  for (i = 0; i < sz; i++)
  {
  printf("%d ", arr[i]);
  }
  return 0;
}


c7ff8be77a1f065f111b8b6a237de9e2_c2665c17856f430f9332681ccee60405.png


降序的思路与升序的思路类似,这里就不加赘叙


3.4.2TOP-K问题


求得数据集合中前K个最大或者最小的元素


方法:


  1. 排序 时间复杂度 O(N*logN)
  2. 建堆->堆排序


当数据集合特别大时,方法1便不可行,数据太大不能在堆上存储数据,只能在磁盘上存储数据。


所以采取方法2


方法2也有两种方式去实现


建大堆 建N个数的大堆,再popK次即可

建小堆,建K个数的小堆,依次遍历后(N-K)个数,如果比堆顶的数据大,便进行交换,再向下调整即可

代码实现 建小堆


void Creatflie(const char* file, int N)
{
  FILE* fin = fopen("test.txt", "w");
  if (fin == NULL)
  {
  perror("fopen fail");
  exit(-1);
  }
  srand((unsigned int)time(0));
  int i = 0;
  for (i = 0; i < N; i++)
  {
  fprintf(fin, "%d\n", rand() % 10);
  }
  fclose(fin);
}
void Printtopk(const char* file, int k)
{
  assert(file);
  FILE* fout = fopen("test.txt", "r");
  if (fout == NULL)
  {
  perror("fopen fail");
  exit(-1);
  }
  int* minHeap = (int*)malloc(k * sizeof(int));
  if (minHeap == NULL)
  {
  perror("malloc fail");
  exit(-1);
  }
  int i = 0;
  //读取前k个元素
  for (i = 0; i < k; i++)
  {
  fscanf(fout, "%d", &minHeap[i]);
  }
  //建k个数的小根堆
  for (i = (k - 2) / 2; i < k; i++)
  {
  Adjustdown(minHeap, k, i);
  }
  //读取N-K个数
  int val = 0;
  while (fscanf(fout, "%d", &val) != EOF)
  {
  if (val > minHeap[0])
  {
    minHeap[0] = val;
    Adjustdown(minHeap, k, 0);
  }
  }
  for (i = 0; i < k; i++)
  {
  printf("%d ", minHeap[i]);
  }
  printf("\n");
  free(minHeap);
  fclose(fout);
}
int main()
{
  const char* file = "test.txt";
  int N = 10;
  int k = 5;
  //创建文件
  Creatflie(file, N);
  Printtopk(file, k);
  return 0;
}


a41895e4c245837bce659d2a2c895058_d79c0794776f4c80a8ef741d81e6bd9d.png


4.二叉树的链式结构及实现


4.1前置说明


二叉树


空树

非空:根节点,根节点的左子树,根节点的右子树组成


4.2二叉树的遍历


4.2.1前序,中序以及后序遍历


二叉树遍历是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。


5900599b461bd3446543e57a9190fabb_32588ceee9704b0aa8a5c8ea84a5d3e1.png


二叉树的遍历有三种方式:前序/中序/后序递归遍历


前序遍历:最先访问根节点,其次访问左子树,右子树

中序遍历:最先访问左子树,其次访问根,右子树

后序遍历:最先访问左子树,其次访问右子树,根


前序遍历


void Preorder(BTnode* root)
{
  if (root == NULL)
  {
  printf("NULL ");
  return;
  }
  assert(root);
  printf("%d ", root->data);
  Preorder(root->left);
  Preorder(root->right);
}


对上面的二叉树进行前序遍历


0c4136d4bdb06af56f6b192c356ae384_b321f93a527143b7ada1b55869a0d646.png


运行结果与上述分析一致


d0a51a2dfb8a78086f3cc6c34a0b43ef_fcb3fcca56ea4e86b72056fb79f8f6d6.png


中序遍历


void Inorder(BTnode* root)
{
  if (root == NULL)
  {
  printf("NULL ");
  return;
  }
  assert(root);
  Inorder(root->left);
  printf("%d ", root->data);
  Inorder(root->right);
}


对上面的二叉树进行中序遍历


c02451b84c52db76783826b5a4d187e3_8c508194198c4bb49a311b8b9eb42072.png


运行结果与上述分析一致


a4ad652fe87b5895b52cfd56bef84618_f0acbb6bff9b492787d71af02d2f040b.png


后序遍历


void Postorder(BTnode* root)
{
  if (root == NULL)
  {
  printf("NULL ");
  return;
  }
  assert(root);
  Postorder(root->left);
  Postorder(root->right);
  printf("%d ", root->data);
}


对上面的二叉树进行后序遍历


dfd8afd92bc42b0bd2f4ad9f71185313_61b4603b7168402fbf0dcd756a20f6f8.png


运行结果与上述分析一致

3fc19163351d6e50f8763d456c980dcb_206b1daf89ad4dd1bfb3eb5b55c4e45a.png


目录
相关文章
|
6天前
|
算法 前端开发
2583. 二叉树中的第 K 大层和
2583. 二叉树中的第 K 大层和
27 0
|
7月前
|
C语言
【二叉树】的实现
【二叉树】的实现
20 0
|
6天前
|
算法 网络协议 NoSQL
认识二叉树(详细介绍)
认识二叉树(详细介绍)
|
6天前
|
存储 数据库管理
【二叉树】
【二叉树】
35 0
|
9月前
|
存储
|
6天前
|
存储
二叉树的实现(下)
二叉树的实现(下)
42 0
|
5月前
|
存储
浅谈二叉树
浅谈二叉树
31 1
|
6月前
24 二叉树
24 二叉树
29 0
|
7月前
|
存储
二叉树讲解
二叉树讲解
48 0
|
7月前
|
存储
二叉树的相关列题!!
二叉树的相关列题!!
47 0