目录
前言
本篇博客对概念性的知识点涉及较少,不建议初学者用于学习。博主将会尽量用图示与代码结合便于读者理解,博主水平不高请见谅,欢迎读者提出建议、批评指正。
二叉树
1.遍历二叉树
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <assert.h> typedef int BTDataType; typedef struct BinaryTreeNode { BTDataType data; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BTNode; BTNode* BuyNode(BTDataType x) { BTNode* node = (BTNode*)malloc(sizeof(BTNode)); if (node == NULL) { perror("malloc fail"); return NULL; } node->data = x; node->left = NULL; node->right = NULL; return node; } BTNode* CreatTree() { BTNode* node1 = BuyNode(1); BTNode* node2 = BuyNode(2); BTNode* node3 = BuyNode(3); BTNode* node4 = BuyNode(4); BTNode* node5 = BuyNode(5); BTNode* node6 = BuyNode(6); node1->left = node2; node1->right = node4; node2->left = node3; node4->left = node5; node4->right = node6; return node1; } void PreOrder(BTNode* root) { if (root == NULL) { printf("NULL "); return; } printf("%d ", root->data); PreOrder(root->left); PreOrder(root->right); } void InOrder(BTNode* root) { if (root == NULL) { printf("NULL "); return; } InOrder(root->left); printf("%d ", root->data); InOrder(root->right); } void PostOrder(BTNode* root) { if (root == NULL) { printf("NULL "); return; } PostOrder(root->left); PostOrder(root->right); printf("%d ", root->data); }
2.二叉树的结点个数
//全局变量 //int size = 0; //void TreeSize1(BTNode* root) //{ // if (root == NULL) // return; // // ++size; // TreeSize1(root->left); // TreeSize1(root->right); //} //传地址 //void TreeSize2(BTNode* root, int* psize) //{ // if (root == NULL) // return; // // ++(*psize); // TreeSize2(root->left, psize); // TreeSize2(root->right, psize); //} //优化 int TreeSize3(BTNode* root) { return root == NULL ? 0 : TreeSize3(root->left) + TreeSize3(root->right) + 1; }
3.求叶子结点的个数
int BTreeLeafSize(BTNode* root) { if (root == NULL) return 0; if (root->left == NULL && root->right == NULL) return 1; return BTreeLeafSize(root->left) + BTreeLeafSize(root->right); }
4.求二叉树的高度
int TreeHeight(BTNode* root) { if (root == NULL) return 0; int leftHeight = TreeHeight(root->left); int rightHeight = TreeHeight(root->right); return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; }
5.求二叉树第K层结点个数
int TreeKLevel(BTNode* root, int k) { assert(k > 0); if (root == NULL) return 0; if (k == 1) return 1; return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1); }
堆
简介:
1.完全二叉树
2.大堆:树的任何一个父母结点都大于等于孩子结点;
小堆:树的任何一个父母结点都小于等于孩子结点。
应用:
1.堆排序--O(N*logN)
//O(N*logN) void HeapSort(int* a, int n) { // 升序 -- 建大堆 // 降序 -- 建小堆 // 建堆--向上调整建堆 /*for (int i = 1; i < n; i++) { AdjustUp(a, i); }*/ // 建堆--向下调整建堆 for (int i = (n - 1 - 1) / 2; i >= 0; --i) { AdjustDown(a, n, i); } // N*logN int end = n - 1; while (end > 0) { Swap(&a[0], &a[end]); // 调整,选出次小的数 AdjustDown(a, end, 0); --end; } }
2.TOP-K
求数据中前K个最大(或最小)的元素 (例如:世界企业500强,高考排行前200,热门专业前10类)
a.堆排序(示例代码见上)
弊端:
(1)需要先创建一个堆
(2)空间复杂度+拷贝数据
b.采用堆排序的思想对数组直接调整
//时间复杂度 -- logN void HeapSort(int* a, int n) { // 向上调整 for (int i = 1; i < n; i++) { AdjustUp(a, i); } // 向下调整 -- O(N) /*for (int i = (n - 1 - 1) / 2; i >= 0; --i) { AdjustDown(a, n, i); }*/ //O(N*logN)--相对于一趟堆排序 int end = n - 1; while (end > 0) { Swap(&a[0], &a[end]); AdjustDown(a, end, 0); end--; } } void TestSort() { int a[] = { 3,1,4,1,0,2,5 }; HeapSort(a, sizeof(a) / sizeof(int)); for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) { printf("%d ", a[i]); } printf("\n"); }
c.局限性
对于N个整数找出最大的前K个,正常思路:把这N个整数建成大堆,Pop K次即可
但是,有些场景下,上述思路无法解决问题(例如,当N非常大的时候--10亿个整数)
解决思路
(1)取前K个数建小堆
(2)剩余的数据依次与堆顶元素比较,如果大于堆顶元素,就替换它进堆
(3)最后这个小堆中的数据就是N个整数中最大的前K个
3.优先级队列
代码:
数组实现堆 + 动态内存管理
1.Heap.h
#pragma once #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> typedef int HPDataType; typedef struct Heap { HPDataType* a; int size; int capacity; }HP; //向上调整 void AdjustUp(HPDataType* a, int child); //向下调整 void AdjustDown(int* a, int n, int parent); void HeapInit(HP* php); void HeapDestroy(HP* php); //插入一个结点 void HeapPush(HP* php, HPDataType x); //删除堆顶的元素 void HeapPop(HP* php); //取堆顶元素 HPDataType HeapTop(HP* php); bool HeapEmpty(HP* php); int HeapSize(HP* php);
2.Heap.c
#include "Heap.h" void HeapInit(HP* php) { assert(php); php->a = NULL; php->capacity = 0; php->size = 0; } void HeapDestroy(HP* php) { assert(php); free(php->a); php->a = NULL; php->capacity = 0; php->size = 0; } void Swap(HPDataType* p1, HPDataType* p2) { HPDataType tmp = *p1; *p1 = *p2; *p2 = tmp; } void AdjustUp(HPDataType* a, int child) { int parent = (child - 1) / 2; //while(parent >= 0) 行不行? while (child > 0) { if (a[child] > a[parent]) { Swap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } } void AdjustDown(int* a, int n, int parent) { 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; } } } //log_2(N) void HeapPush(HP* php, HPDataType x) { assert(php); //如满拓展空间 if (php->size == php->capacity) { int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2; HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType)); if (tmp == NULL) { perror("realloc fail"); return; } php->a = tmp; php->capacity = newCapacity; } php->a[php->size] = x; php->size++; AdjustUp(php->a, php->size - 1); } void HeapPop(HP* php) { assert(php); assert(!HeapEmpty(php)); Swap(&php->a[0], &php->a[php->size - 1]); php->size--; AdjustDown(php->a, php->size, 0); } HPDataType HeapTop(HP* php) { assert(php); assert(!HeapEmpty(php)); return php->a[0]; } bool HeapEmpty(HP* php) { assert(php); return php->size == 0; } int HeapSize(HP* php) { assert(php); return php->size; }
图示助解:
a.HeapPush(插入一个元素)——时间复杂度O(logN)
对于完全二叉树,已知一个结点的下标n如何找出它的父母结点和左右孩子的结点呢?
parent = (n - 1) / 2;
leftchild = n * 2 + 1, rightchild = n * 2 + 2;
b.HeapPop(删除堆顶元素)——时间复杂度O(logN)
3.Test.c
#include "Heap.h" int Test0() { HP hp; HeapInit(&hp); int a[] = { 65,100,21,32,520,64 }; for (int i = 0; i < sizeof(a) / sizeof(int); ++i) { HeapPush(&hp, a[i]); } while (!HeapEmpty(&hp)) { int top = HeapTop(&hp); printf("%d\n", top); HeapPop(&hp); } return 0; } //对数组进行堆排序 // 可以这么玩吗?--可以 // 弊端: // 1、需要先创建一个堆,太麻烦 // 2、空间复杂度+拷贝数据 // //void HeapSort(int* a, int n) //{ // HP hp; // HeapInit(&hp); // // N*logN // for (int i = 0; i < n; ++i) // { // HeapPush(&hp, a[i]); // } // // // N*logN // int j = 0; // while (!HeapEmpty(&hp)) // { // int top = HeapTop(&hp); // a[j++] = top; // HeapPop(&hp); // } // // HeapDestroy(&hp); //} //O(N*logN) void HeapSort(int* a, int n) { // 升序 -- 建大堆 // 降序 -- 建小堆 // 建堆--向上调整建堆 /*for (int i = 1; i < n; i++) { AdjustUp(a, i); }*/ // 建堆--向下调整建堆 for (int i = (n - 1 - 1) / 2; i >= 0; --i) { AdjustDown(a, n, i); } // N*logN int end = n - 1; while (end > 0) { Swap(&a[0], &a[end]); // 再调整,选出次小的数 AdjustDown(a, end, 0); --end; } } void TestSort() { int a[] = { 3,1,8,4,5,2,0 }; HeapSort(a, sizeof(a) / sizeof(int)); for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) { printf("%d ", a[i]); } printf("\n"); } int main() { //Test0(); TestSort(); return 0; }