一、树
树是一种非线性数据结构,它由节点(node)和连接这些节点的边(edge)组成。树的基本结构类似于真实世界中的树,有根(root)、枝干(branches)、叶子(leaves)等概念。
树结构中有几个重要的概念:
-
根节点(Root):树的顶端节点,是树的起始点,其上没有父节点。
-
父节点(Parent):一个节点连接着它下面的节点,这个节点就是下面节点的父节点。
-
子节点(Child):一个节点下面连接着的节点,这些节点就是该节点的子节点。
-
叶子节点(Leaf):没有子节点的节点称为叶子节点,也可以简称为叶子。
-
树的高度(Depth):从根节点到该节点的唯一路径上的节点数目。
-
节点的高度(Height):从该节点到叶子节点的最长路径上的节点数目。
-
子树(Subtree):树中的任意一个节点和其所有子节点组成的结构。
-
度:在树结构中,度是指一个节点拥有的子节点的数量。节点的度可以分为入度和出度两种情况:
- 入度:对于有向树(Directed Tree),一个节点的入度是指指向该节点的边的数量。
- 出度:一个节点的出度是指从该节点出发的边的数量。
树结构的应用非常广泛,在计算机科学中,树被用于实现各种数据结构和算法,如二叉搜索树、堆、树状数组等。在数据库系统中,树结构也被用于构建索引,提高数据的检索效率。在人工智能领域,决策树被用于分类和回归分析等任务。
二、二叉树和堆
1、与单纯的树不同,二叉树的一个父节点的最多有两个子节点(也可能是两颗子树),而二叉树中又有完全二叉树和满二叉树,完全二叉树至多有一个度为一的结点,而且叶子结点是从左向右分布。
堆就是通过模拟完全二叉树完成的,但是底层是数组,因为完全二叉树的父节点与子节点排列顺序存在一个规律。
有了这个规律我们就可以像操作二叉树一样操作数组。
2、大堆和小堆
在小堆中父节点的值是小于子节点的,而大堆中父节点是大于子节点的。
3、建堆
建堆的过程就是调整子节点和父节点的关系
这几个图是连续的,圈起来的数字是表示步骤顺序
过程知道了,代码怎么写
首先搞一个结构体,嫌麻烦可以不搞,准备一个数组就好了
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int Data;
typedef struct STP
{
Data* x;
int top;//元素个数
int size;
int capacity;//数组大小
}TSP;
void swap(Data* x,Data* y);//交换
void adjust1(TSP* root, int i, int end);//下调
void adjust2(TSP* root, int i);//上浮
void Bypile1(TSP* root, int size);//size是元素个数,root是结构体指针,adjust
void HeapSort1(TSP* proot, int size);
void Bypile2(TSP* root, int size);
void adjsut3(TSP* proot, int i, int end);//下调
void adjsut4(TSP* proot, int end);//上浮
void HeapSort2(TSP* proot, int size);
void print(TSP* root, int size);
void insertion(TSP* proot, Data x);//插入数据
我有两个版本
先看第一个
递归版本
调整上浮的函数adjsut1
void adjust2(TSP* proot, int i)//插入和建堆用,上浮
{//参数proot是结构体指针,i 表示是调整到那个位置了,
//Data* a = proot->x + end-1;
int parent = (i-1)/2;//先找一下他爹
if (proot->x[i] < proot->x[parent])//我们判断当前要调整的元素,
{//要建大堆是爹比儿子大,建小堆是儿子比爹大。我这里建小堆
swap(&(proot->x[i]), &(proot->x[parent]));//比他爹小就调整,交换
}
else//比它爹大就说明是符合小堆的,不用调
return;
i = parent;//这个是多此一举
adjust2(proot, i);//递归子问题,因为每一颗子树都可以是一个堆
}
建堆
void Bypile1(TSP* proot,int size)//建堆
{
int end = 1;//这个end是用来限制调整范围,也就是图中的 i
while (end<size)//枚举下表为 1 到最后一个数字
{
adjust2(proot, end);//对每一个数字进行调整
end++;
}
}
插入函数
void insertion(TSP* proot, Data x)//插入
{
Data* a = NULL;
if (proot->x==NULL)
{
a = (Data*)malloc(sizeof(Data));
proot->capacity =1;
}
if (proot->size == proot->capacity)
{
a = (Data*)realloc(proot->x,2 * (proot->capacity) * sizeof(Data));
proot->capacity =2*(proot->capacity);
assert(a);
}
if (a != NULL)
proot->x = a;
//proot->x[++proot->top] = x;
assert(proot->x);
proot->x[++proot->top] = x;
proot->size++;
adjust2(proot,proot->top);
//adjsut4(proot, proot->top);
}
测试
TSP root = {NULL,-1,0,0};
TSP* proot = &root;
insertion(proot,2);
insertion(proot,5);
insertion(proot,7);
insertion(proot,1);
insertion(proot,4);
insertion(proot, 6);
insertion(proot, 3);
insertion(proot, 9);
insertion(proot, 20);
insertion(proot, 18);
insertion(proot, 12);
非常的OK
堆排序
在这个小堆中堆顶的元素是最小的,我们把这个元素和最后一个元素交换,再调整这个堆,然后再把堆顶元素和倒数第二个元素交换,这样后面一部分的元素就是升序排列,重复这个过程一直交唤到第一个元素那么数组就变得有序了。
这是一个小堆
最终结果
代码实现
void adjust1(TSP* proot,int i,int end)//下调
{//参数proot是结构体指针,i 表示是调整到那个位置了,end是限制范围的
//Data* a = proot->x + end-1;
int child = 2 * i + 1;
if (2 * i + 1 > end )//左右子树都没有
return;
if (2 * i + 2 <= end )
{//左右子树都有,选小的(建小堆就要选小的)
child = (proot->x[2 * i + 1]) < (proot->x[2 * i + 2]) ? 2 * i + 1 : 2 * i + 2;
}
if (proot->x[child] < proot->x[i])//建大堆我们判断当前要调整的元素,
{/*/建小堆就要用小于号*/ //是否大于堆顶元素。
swap(&(proot->x[i]), &(proot->x[child]));//比堆顶元素大就调整,交换
i = child;
}
else//如果父节点比子节点小就已经是小堆了
return;
adjust1(proot, i,end);//递归子问题,因为每一颗子树都可以是一个堆
}
void HeapSort1(TSP* proot, int size)
{
Bypile1(proot, size);
int i = size-1, tmp = 0;
while (i > 0)
{
swap(&(proot->x[i]), &(proot->x[0]));
i--;//先减减还是先调整,看你自己,如果懂了,参数应该也知道怎么调
adjust1(proot,0,i);
}
}
结果
循环的版本,思路是一样的
void HeapSort2(TSP* proot,int size )
{
Bypile2(proot, size);
int i = size-1,tmp=0;
while (i > 0)
{
swap(&(proot->x[i]), &(proot->x[0]));
adjsut3(proot,0,i);
i--;
}
}
void Bypile2(TSP* proot, int size)
{
for(int i = (size-1)/2; i >= 0 ; i--)
{
adjsut3(proot, i,size-1);//下调
}
}
void adjsut3(TSP* proot,int i,int end)//下调
{
int child = 2*i+1,parent=i;
while (child < end)
{
if (2 * parent + 2 < end && proot->x[child] > proot->x[child + 1])
++child;
if (proot->x[child] < proot->x[parent])
{
swap(&(proot->x[child]), &(proot->x[parent]));
parent = child;
child = 2 * parent + 1;
}
else
break;
}
}
void adjsut4(TSP* proot, int end)//上浮
{
int parent = (end-1) / 2,child = end;
while (parent >= 0)
{
if (proot->x[child] < proot->x[parent])
{
swap(&(proot->x[child]), &(proot->x[parent]));
child = parent; parent = (child-1) / 2;
}
else
break;
}
}
删除、销毁等的函数可以自己写和链表什么的差不多
接一个题目 . - 力扣(LeetCode)