数据结构 = 结构定义 + 结构操作
树形结构
树的深度(从上至下)
树的高度(从下至上)
节点数量 = 边数+1
二叉树
- 每个节点的度最多为2
- 度为0的节点比度为2的节点多一个:
- 通过左孩子右兄弟的方法可以将N叉树转换为二叉树
二叉树 - 遍历
- 前序遍历: 根 左 右
- 中序遍历: 左 根 右
- 后序遍历: 左 右 根
二叉树分类
国内教材分为完全二叉树和满二叉树
国外教材分为完全二叉树, 满二叉树()和完美二叉树(等同于国内的满二叉树)
二叉树 - 完全二叉树
完全二叉树展现的计算思维是: 记录式转计算式的思维, 是一种算法优化手段
二叉树 - 广义表
代码演示
// binary_tree_coding
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 结构定义
typedef struct Node {
int val;
struct Node *lchild, *rchild;
}Node;
typedef struct Tree {
Node *root;
int n; // 树存储的节点数
}Tree;
// 初始化
Node *getNewNode(int val){
Node *p = (Node*)malloc(sizeof(Node));
p->val = val;
p->lchild = p->rchild = NULL;
return p;
}
Tree *getNewTree() {
Tree *tree = (Tree*)malloc(sizeof(Tree));
tree->n = 0;
tree->root = NULL;
return tree;
}
// 销毁
void clearNode(Node *node){
if (node==NULL) return;
clearNode(node->lchild);
clearNode(node->rchild);
free(node);
return ;
}
void clearTree(Tree* tree){
if (tree==NULL) return;
clearNode(tree->root);
free(tree);
return ;
}
// 排序二叉树
Node *insertNode(Node *root, int val, int *ret){
if (root==NULL) {
*ret = 1;
return getNewNode(val);
}
if (root->val==val) return root;
if (root->val>val) root->lchild = insertNode(root->lchild, val, ret);
else root->rchild = insertNode(root->rchild, val, ret);
return root;
}
void insert(Tree *tree, int val){
int flag = 0;
tree->root = insertNode(tree->root, val, &flag);
tree->n += flag;
return ;
}
// 二叉树转广义表
void outputNode(Node* root){
if (root==NULL) return;
printf("%d", root->val);
if (root->lchild==NULL && root->rchild==NULL) return;
printf("(");
outputNode(root->lchild);
printf(",");
outputNode(root->rchild);
printf(")");
return ;
}
void outputTree(Tree *tree){
printf("tree(%d) = ", tree->n);
outputNode(tree->root);
printf("\n");
return ;
}
// 前序遍历
void preorderNode(Node *node) {
if (node==NULL) return;
printf("%d ", node->val);
preorderNode(node->lchild);
preorderNode(node->rchild);
return ;
}
void preorder(Tree *tree) {
printf("preorder : ");
preorderNode(tree->root);
printf("\n");
}
// 中序遍历
void inorderNode(Node *node) {
if (node==NULL) return;
inorderNode(node->lchild);
printf("%d ", node->val);
inorderNode(node->rchild);
return ;
}
void inorder(Tree *tree) {
printf("inorder : ");
inorderNode(tree->root);
printf("\n");
}
// 后序遍历
void postorderNode(Node *node) {
if (node==NULL) return;
postorderNode(node->lchild);
postorderNode(node->rchild);
printf("%d ", node->val);
return ;
}
void postorder(Tree *tree) {
printf("postorder : ");
postorderNode(tree->root);
printf("\n");
}
// 主函数
int main() {
srand(time(0));
Tree *tree = getNewTree();
for (int i=0; i<10; i++) {
int val = rand()%100;
insert(tree, val);
outputTree(tree);
}
preorder(tree);
inorder(tree);
postorder(tree);
clearTree(tree);
system("pause");
return 0;
}
堆与优先队列
堆本质上是一个完全二叉树, 一个堆就可以对应一个连续的数组
- 大顶堆
- 小顶堆
堆 - 尾部插入调整
堆在进行插入操作时, 时间复杂度为次(n是堆内部的元素数量)
堆 - 头部弹出调整
堆排序
时间复杂度为 (n是堆内部的元素数量)
口诀:
- 将堆顶元素与堆尾元素交换
- 将此操作看作是堆顶元素弹出操作
- 按照头部弹出以后的策略调整堆
堆与优先队列
优先队列的底层就是堆, 都是从尾部进入, 从头部出去, 优先队列中优先的含义是每次出队的都是全局最值
| 普通队列 | 最大(最小)堆 |
|---|---|
| 尾部出队 | 尾部可以插入 |
| 头部出队 | 头部可以弹出 |
| 先进先出 | 每次出队权值(最值元素) |
| 数组实现 | 数组实现, 逻辑上看成一个堆 |
题目
LEETCODE 703 23 295 264 313
代码实现
// heap_sort
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define swap(a, b) { \
__typeof(a) __temp = a; \
a = b, b = __temp; \
}
void downUpdate(int *arr, int n, int ind){
while ((ind<<1)<=n) {
int temp = ind, l=ind<<1, r=ind<<1|1;
// 从小到大排序建立大顶堆
if (arr[l]>arr[temp]) temp=l;
if (r<=n && arr[r]>arr[temp]) temp=r;
if (temp==ind) break;
swap(arr[temp], arr[ind]);
ind = temp;
}
return;
}
void heap_sort(int *arr, int n) {
// 改变arr的下标, 从0开头变为从1开头
arr -= 1;
for (int i=n>>1; i>=1; --i) {
downUpdate(arr, n, i);
}
for (int i=n; i>1; --i){
swap(arr[1], arr[i]);
downUpdate(arr, i-1, 1);
}
return ;
}
void output(int *arr, int n) {
printf("arr[%d] = [", n);
for (int i=0; i<n; i++){
printf("%d ", arr[i]);
}
printf("]\n");
return ;
}
int main(){
srand(time(0));
#define MAX_OP 20
int *arr = (int*)malloc(sizeof(int)*MAX_OP);
for (int i=0; i<MAX_OP; i++){
int val = rand()%100;
arr[i] = val;
}
output(arr, MAX_OP);
heap_sort(arr, MAX_OP);
output(arr, MAX_OP);
return 0;
}
排序与查找
稳定排序(插入, 冒泡, 归并)
若=(i<j), 但是排序之后二者的相对顺序不变, 也就是说仍然在前面, 则称为稳定排序算法
- 插入
- 冒泡
- 归并
插入排序
口诀:
- 将数组分成"已排序区域"和"未排序区域"
- 将"未排序区域"的头部元素, 插入到"已排序区域"中
- 直到"未排序区域"没有元素为止
平均时间复杂度: O()