文章目录
1.树
定义:一种特殊的数据结构
树的特点:具有多个节点;每个节点都有一个或多个指针;他的形状像一个倒立的树;
*2.树的元素
结点的度:一个节点包含子树的个数;(及这个节点链接的指针个数)。
叶节点或终端节点:度数为0的节点称做叶节点;
非终端节点或分支节点:度不为0的节点;
根节点:为没有父节点的特殊节点;
双亲节点或父节点:一个指针的上段的元素就是父节点
孩子节点或子节点:一个指针的下端元素为子节点;
兄弟节点:具有相同父节点的节点互相称为兄弟节点;(亲兄弟)
树的度: 一个树中,最大的节点的度为这个树的度;
节点的层次:根的那一层为第一层;子节点的层数为2层,后面一次类推;
树的高度或深度:树中节点的最大层次
堂兄弟节点:双亲在同一层的节点互为堂兄弟;
节点的祖先:节点分支给该节点以上的所有节点
子孙:该节点以下分支的个元素;(必须是情的)
这里层数为什么要从1开始:
** 数组的下标我为什么从0开始:**
原因:数组下表的本质是:指针加一个下标子再解引用:*(a+i);
3.树的结构
结构本质:父节点和N棵子树,这里的子树的内容也是有父节点和N个子树;因次树是递归定义的;
树的结构小特点:树的子树之间不得相交;(原因:每个子树不能有两个父亲)。
4.树的代码实现
实现方法:(这里的实现方法都是使用链表来实现,原因:二叉树除了堆以外都是使用链表来实现)
1.如果明确树的度,那么可以定义(一个元素定义元素应该放的数据和子树的指针;
2.使用顺序表来存储孩子;(这里的是顺序表结构体;这里的数组必须包含盖子来奶的所有地址)
3.双亲表示法(每个位置只存父的指针或下标)
4.左孩子 右兄弟表示法
结构体的组成:兄弟指针和子指针和自己的数;、
普遍示例:
实践示例:
这里的每一次双击都是由孩子指针来访问孩子;
这里有一个比喻:一个人生了孩子,每一代的孩子大的带小的;
孩子再生孩子;
讲述树可以进行尾删和首插;这里就好比wendios系统的文件的删除和添加;
5.二叉树
5.1基本概念
**1.概念 **:二叉是指每个元素都有两个子指针;
2.分类:满二叉树;完全二叉树
2.1满二叉树:每一层都是满的;
2.2完全二叉树:最深的层的元素大于1小于最深放满元素的个数;
3.N行二叉树元素的个数和(使用
等比数列公式可以经计算,错位相乘法):
一个满二叉树的元素个数公式:2^h-1;
一个完全二叉树的元素个数公式:2^(h-1) { 这里是最后一行为1}<x<2 ^(h-1)
计算过程:
3.1满二叉树
3.2完全二叉树
5.2二叉树的储存结构
分类:数组结构;链式结构;
5.2.1顺序结构(即数组结构)
1.数组结构的特点:
父子之间的关系:
- 父节点的下标找孩子:
Leftchild=parent*2-1;
Rightchild=parent*2+2 - 孩子找父亲
Parent=(child-1)/2
这里的孩子比长辈大;原因是:子辈的数组下表靠后;
** 2顺序结构的应用**
只适用完全二叉树:原因:不完全二叉树会有空间浪费
5.2.2 链式结构
二叉链;
三叉链;
5.3二叉树的应用
1.堆 ————选数(6.讲)
2.搜索树—AVL树—红黑树(后两者是搜索树的改进)
搜索树的特征是:左子树比根小;右子树比根大:
6.堆
6.1堆的基本概念
1本质:
完全二叉树(这里使用数组来实现)
2.分类(有大小顺序来分类)
大堆:树的任意一个父亲都大于或等于孩子
物理结构:数组
逻辑结构:二叉树
小堆:树任意一个父亲都小于等于孩子
物理结构:数组
逻辑结构:二叉树
3.应用 (后边会解释)
1.堆排序——O(N*logN)
2.Topk
3.优先级队列
6.2堆的代码实现
6.2.1堆的代码
这里讲述了
向下调整建堆(这里的需要该元素一下的子树都为大堆货小堆;应用:删除元素后的首元素的调整;
向上调整建堆
//Heap.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDateType;
typedef struct heap
{
HPDateType* a;
int size;
int capacity;
}HP;
//这里的每个元素不需要组成结构体;原因是这里的每个元素是由一个变量组成;(
// 堆的构建
void HeapInit(HP* php);
// 堆的销毁
void HeapDestroy(HP* php);
// 堆的插入
void HeapPush(HP* php, HPDateType x);
// 堆的删除
void HeapPop(HP* php);
// 取堆顶的数据
HPDateType HeapTop(HP* php);
// 堆的数据个数
int HeapSize(HP* php);
// 堆的判空
bool HeapEmpty(HP* php);
//Heap.c
void HeapInit(HP* php)
{
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
void HeapDestroy(HP* php)
{
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
void Swap(HPDateType* p1, HPDateType*p2)
{
HPDateType p = *p1;
*p1 = *p2;
*p2 = p;
}
void AdjustUp(HPDateType* a, int child)
{
while (child>0)//这里的判断不要使用父节点原因:这里的子节点为0时;父节点的值为-1/2;不为整数不便使用这里的-1/2的由于取整数所以为0;
{
if (a[(child - 1) / 2] > a[child])
{
/*HPDateType n = 0;
n = a[(child - 1) / 2];
a[(child - 1) / 2] = a[child];
a[child] = n;
child = (child - 1) / 2;*/
Swap(&a[(child - 1) / 2], &a[child]);
child = (child - 1) / 2;
}
else
{
break;
}
}
}
void AdjustDown(int* a, int n, int parent)
{
int child = parent * 2 +1;
while (child<n)//判断child是否为树枝;直到遍历到数组的最后;
{
//选择左右孩子中的小的那个,这里先选择左边的为小;
if(child+1<n&&a[child+1]<a[child])//这里有越界的可能;所以需要判断右子树是否越界;
{
++child;
}
//判断树元素的大小并进行交换
if (a[parent] <a[child])
{
break;
}
else
{
Swap(&a[parent],&a[child]);
parent = child;
child= parent * 2 + 1;
}
}
}
void HeapPush(HP* php, HPDateType x)
{
assert(php);
if (php->size == php->capacity)
{
int newcapacity = php->size == 0 ? 4 : 2 * php->capacity;
HPDateType* tmp = (HPDateType*)realloc(php->a, newcapacity * sizeof(HPDateType));
if (tmp == NULL)
{
perror("realloc失败");//这里tmp指针只是为了判断是否realloc是否成功,即父辈等于子辈;
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size] = x;//这里的数量正好是下位的下表
php->size++;
//排序
AdjustUp(php->a, php->size - 1);
}
// 堆的删除
void HeapPop(HP* php)
{
//1.assert 判断堆是否为空
assert(php);
assert(!HeapEmpty(php));
//2.交换
Swap(&php->a[0], &php->a[php->size - 1]);
//3.删除
php->size--;
//4.调整(向下调整)
AdjustDown(php->a, php->size, 0);//这里的parent设置为0是从根来排序;
}
// 取堆顶的数据
HPDateType HeapTop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
return php->a[0];
}
// 堆的数据个数
int HeapSize(HP* php)
{
assert(php);
return php->size;
}
// 堆的判空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
//main.c
int main()
{
HP heap;
HeapInit(&heap);
int a[] = { 0,9,2,3,4,5, };
for (int i = 0; i < sizeof(a)/sizeof(int); i++)
{
HeapPush(&heap, a[i]);
}
while (!HeapEmpty(&heap))
{
int top = HeapTop(&heap);
printf("%d\n", top);
HeapPop(&heap);
}
return 0;
}
6.2.2堆的时间复杂度**:
由于有添加和删除两个函数;都需要排序是整个元素成为一个树;将连个遍历相加。logN的时间复杂度;logN
堆的应用
这里需要与qsort函数比较,TOP-K问题是部分进行调试;
6.2.2堆的应用
6.2.2.1堆排序
6.2.2.1.1建堆版
上面的代码没有将数组本身进行改变,而是改变的一个堆内部元素的顺序;下面我将讲述改变数组的方法:
//.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDateType;
typedef struct heap
{
HPDateType* a;
int size;
int capacity;
}HP;
//这里的每个元素不需要组成结构体;原因是这里的每个元素是由一个变量组成;(
// 堆的构建
void HeapInit(HP* php);
// 堆的销毁
void HeapDestroy(HP* php);
// 堆的插入
void HeapPush(HP* php, HPDateType x);
// 堆的删除
void HeapPop(HP* php);
// 取堆顶的数据
HPDateType HeapTop(HP* php);
// 堆的数据个数
int HeapSize(HP* php);
// 堆的判空
bool HeapEmpty(HP* php);
//.c
#include"Heap.h"
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
void Swap(HPDateType* p1, HPDateType*p2)
{
HPDateType p = *p1;
*p1 = *p2;
*p2 = p;
}
void AdjustUp(HPDateType* a, int child)
{
while (child > 0)//这里的判断不要使用父节点原因:这里的子节点为0时;父节点的值为-1/2;不为整数不便使用这里的-1/2的由于取整数所以为0;
{
if (a[(child - 1) / 2] < a[child])
{
/*HPDateType n = 0;
n = a[(child - 1) / 2];
a[(child - 1) / 2] = a[child];
a[child] = n;
child = (child - 1) / 2;*/
Swap(&a[(child - 1) / 2], &a[child]);
child = (child - 1) / 2;
}
else
{
break;
}
}
}
void AdjustDown(int* a, int n, int parent)
{
int child = parent * 2 +1;
while (child<n)//判断child是否为树枝;直到遍历到数组的最后;
{
//选择左右孩子中的小的那个,这里先选择左边的为小;
if(child+1<n&&a[child+1]>a[child])//这里有越界的可能;所以需要判断右子树是否越界;
{
++child;
}
//判断树元素的大小并进行交换
if (a[parent] >a[child])
{
break;
}
else
{
Swap(&a[parent],&a[child]);
parent = child;
child= parent * 2 +1;
}
}
}
void HeapPush(HP* php, HPDateType x)
{
assert(php);
if (php->size == php->capacity)
{
int newcapacity = php->size == 0 ? 4 : 2 * php->capacity;
HPDateType* tmp = (HPDateType*)realloc(php->a, newcapacity * sizeof(HPDateType));
if (tmp == NULL)
{
perror("realloc失败");//这里tmp指针只是为了判断是否realloc是否成功,即父辈等于子辈;
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size] = x;//这里的数量正好是下位的下表
php->size++;
//排序
AdjustUp(php->a, php->size - 1);
}
// 堆的删除这里删除的是堆的堆顶元素;
void HeapPop(HP* php)
{
//1.assert 判断堆是否为空
assert(php);
assert(!HeapEmpty(php));
//2.交换
Swap(&php->a[0], &php->a[php->size - 1]);
//3.删除
php->size--;
//4.调整(向下调整)
AdjustDown(php->a, php->size, 0);//这里的parent设置为0是从根来排序;
}
// 取堆顶的数据
HPDateType HeapTop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
return php->a[0];
}
// 堆的数据个数
int HeapSize(HP* php)
{
assert(php);
return php->size;
}
// 堆的判空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
//main.c
#include"Heap.h"
//int main()
//{
// HP heap;
// HeapInit(&heap);
//
// int a[] = { 0,9,2,3,4,5, };
// for (int i = 0; i < sizeof(a)/4; i++)
// {
// HeapPush(&heap, a[i]);
// }
// while (!HeapEmpty(&heap))
// {
// int top = HeapTop(&heap);
// printf("%d\n", top);
// HeapPop(&heap);
// }
//
// return 0;
//}
void HeapSort(int* a, int n)
{
//升序--建大堆
//降序--建立堆
HP heap;
HeapInit(&heap);
for (int i = 0; i < n/*sizeof(a) / 4这里不要使用size(数组名)原因:这里的数组名是数组的首元素地址*/ ; i++)
{
HeapPush(&heap, a[i]);
}
int i = 0;
while (!HeapEmpty(&heap))
{
int top = HeapTop(&heap);
a[i++] = top;
HeapPop(&heap);
}
HeapDestroy(&heap);
}
int main()
{
int a[] = { 7,8,3,5,1,9,5,4 };
HeapSort(a, sizeof(a)/sizeof(int));
return 0;
}
这种方法可以实现
弊端:
1.前提要建立一个堆;
2.空间复杂度较高;创立了堆
优点:
时间复杂度较低;
6.2.2.1.2改数组为堆版
直接将数组改为堆;这样就会减少空间复杂度;
这里的数组排序使用的是堆排序(特点:升序排大堆,降序排小堆)这里使用小堆后首位的为对内最小的,但是这种情况子树无法排序,所有使用将选出来的值放到后面;
原因:这里由于要多次使用堆排序;如果使用使用升序来排小堆;第二次以后的排序会出现错误;堆的父子关系会发生变化;
//,h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDateType;
typedef struct heap
{
HPDateType* a;
int size;
int capacity;
}HP;
//这里的每个元素不需要组成结构体;原因是这里的每个元素是由一个变量组成;(
// 堆的构建
void HeapInit(HP* php);
// 堆的销毁
void HeapDestroy(HP* php);
// 堆的插入
void HeapPush(HP* php, HPDateType x);
// 堆的删除
void HeapPop(HP* php);
// 取堆顶的数据
HPDateType HeapTop(HP* php);
// 堆的数据个数
int HeapSize(HP* php);
// 堆的判空
bool HeapEmpty(HP* php);
void Swap(HPDateType* p1, HPDateType* p2);
void AdjustDown(int* a, int n, int parent);
void AdjustUp(HPDateType* a, int child);
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = 0;
php->capacity = 0;
}
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->capacity = php->capacity = 0;
}
void Swap(HPDateType* p1, HPDateType* p2)
{
HPDateType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustUp(HPDateType* 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[parent], &a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
// logN
void HeapPush(HP* php, HPDateType x)
{
assert(php);
if (php->size == php->capacity)
{
int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDateType* tmp = (HPDateType*)realloc(php->a, newCapacity * sizeof(HPDateType));
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);
}
// logN
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);
}
HPDateType 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;
}
``void HeapSort(int* a, int n)
{
/*升序--建大堆
降序--建立堆*/
for (int i = 1; i < n; i++)/*sizeof(a) / 4这里不要使用size(数组名)原因:这里的数组名是数组的首元素地址*/
{
AdjustUp(a, i);
}
int end=n-1;
while (end>0)
{
Swap(&a[0],&a[end]);
AdjustDown(a, end,0);
end--;
}
}
int main()
{
int a[] = { 7,8,3,5,1,9,5,4 };
HeapSort(a, sizeof(a)/sizeof(int));
return 0;
}
这个代码的时间复杂度是NlogN的时间复杂度;
对比NlogN和N^2的时间复杂度的对比;
注意:
向下排序和向上排序都使用结构体中的元素来代称;而不是直接传结构指针;原因:这里可以在数组中使用;
这里的降序建小堆,升序建大堆;升序和降序是形成后的数组顺序;
6.2.2.1.3堆排序的时间复杂度
向下调整和向上调整的比较
向上调整:
向下调整
** 向下调整建堆和向上调整建堆**
向上调账建堆
向下调整建堆
时间内复杂度
向上调整建堆:
O(N*logN)
向下调整建堆
O(N)
终结:向下调整舰的时间复杂度低,由此可见实际情况下多用向下调整舰队;
6.2.2.2 Top k问题
解决的问题场景:材料数据过于巨大;想要找到前k个最大/最小的值;
这时使用正常的逻辑无法实现(建立一个大堆,从而找到前k个值)原因是这里遍历一边内存占的过于大;
改进思路
建立一个个数为k的小堆;
再将后N-k个数依次比较和 对顶比较(如果改值比对顶数据大,就将其替换)