1.数的概念及结构
2.二叉树的概念及结构
1.树的概念及结构
1.1树的概念:
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。称作树是因为结构上看起来像一棵树,根在上面,叶在下面。
1.有一个特殊结点,称为根结点,根结点没有前驱节点
2.除根结点外,其余结点被分成M(M>0)个互不相交的集合,每个结点又是一颗结构与树类似的子树。每个子树的根结点有且只有一个前驱,可以有0或者多个后继(连着很多点)。树是递归定义的(每个根都有N(N>=0)个子树)。(树形结构中,子树之间不能有交集,否则就不是树形结构)
1.2树的特点:
1.3树的相关概念:
结点的度:一个结点含有的子树的个数称为该结点的度;如上图:A的度为6
叶结点或终端结点:度为0的结点称为叶结点;如上图B,C,H,I,L,M,N,K,P,Q是没有后继的
非终端结点或分支结点:度不为0的结点;如上图:D,E,F,G等有后继的
双亲结点或父结点:若一个结点含有子节点,则这个结点叫这个子节点的父节点;如上图,A是B的父节点
孩子结点或子节点:一个结点含有的子树的根结点称为该结点的子节点;如上图:B是A的子节点
兄弟结点:具有相同的父节点的结点称为兄弟结点;如上图:B,C是兄弟结点
树的度:一棵树中,最大的结点的度称为树的度;上图最多结点的是A,有六个结点,度为6
结点的层次:从根开始定义,根为第一层,根的子节点为第二层,往后推
树的高度或深度:树中结点的最大层次;上图最大层次为4,所以树的深度为4
堂兄弟结点:双亲在同一层次的结点互为堂兄弟;H,I互为堂兄弟
结点的祖先:从根到该结点所经分支上的所有结点;A是所有结点的祖先
子孙:以某一结点为根的子树任意一结点都为该结点的子孙。所有的结点都是A的子孙
森林:由m(m>0)课互不相交的树的集合叫森林
树的层次最上面可以是0层也可以是第一层,俩个都是可以的,一般把最上面定义为第一层。
数组第一个元素下标为0的原因是a[i]=*(a+i),而数组名是首元素的地址,首元素地址加一的话就是第二个元素而不是第一个元素,a[1]=*(a+1)
2.二叉树的概念及结构
2.1概念
一棵二叉树是结点的一个有限集合,该集合:
1.为空
2.由一个根结点加上俩颗别称为左子树和右子树的二叉树组成
图知:
1。二叉树不存在大于度为2的点,没有哪一个结点有俩个以上的儿子
2.二叉树的子树是左右之分的,顺序是不能颠倒的,因此二叉树是有序的树
以下是二叉树可能存在的情况:
2.2特殊的二叉树
1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
说,如果一个二叉树的层数为K,且结点总数是 2^k-1,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
计算满二叉树总结点个数过程:
数组来存储二叉树:
可以把二叉树放进数组里面,一层一层从左到右的放进去,同时用数组来存储二叉树也存在一些规律。
规律:
设父亲在数组中的下标为:i
则左孩子在数组中的下标为:2*i+1
右孩子在数组中下标为:2*i+2
假设孩子在数组的下标为:j
则父亲在数组的下标为:(j-1)/2(因为无论是左孩子还是右孩子都是一样的结果 左孩子 j=4 右孩子为j+1=5 带入(j-1)/2 一个是2一个是2.5 但在下标中都是2 因为下标是整数 )
上面适用与满二叉树与完全二叉树,在其余的二叉树用数组时要假设为满二叉树孩子完全二叉树,不然上面的规律无法使用。
但这样子也会浪费空间,如上图。
2.3二叉树中的堆
堆有俩种,一是大堆,二是小堆。
大堆的条件,首先是完全二叉树(满二叉树是特殊的完全二叉树),其次是如何一个父结点的值要大于其子结点的值,根是最大的。
小堆的条件,首先是完全二叉树,任何一个父结点的值小于其子结点的值,根是最小的。
struct TreeNode
{
int val;
struct TreeNode* Leftchild;
struct TreeNode* RightBrother;
};
通过左孩子又兄弟的方法可以把二叉树的结点联系起来 。
2.4堆的代码实现
以下是小堆的代码实现,大堆的实现仅需要把AdjustDown与AdjustUp函数中的比大小符号改反即可。
Heap.h文件:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
void Swap(HPDataType* p1, HPDataType* p2);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);
void HPInit(HP* php);
void HPDestroy(HP* php);
void HPPush(HP* php, HPDataType x);
void HPPop(HP* php);
HPDataType HPTop(HP* php);
bool HPEmpty(HP* php);
Heap.c文件:
#include"Heap.h"
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp=0;
tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustUp(HPDataType* a, int child)
{
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 AdjustDown(HPDataType* a, int n, int parent)
{
int child = 2 * parent + 1;//假设为左孩子
//while (child<n&&child+1<n)这样子不行是因为child+1>n的话只能说明没有兄弟 child还是存在,还是可能比父亲小
while(child<n)
{
if (child+1<n&&a[child] > a[child + 1])//要在这里进行判断 child+1是否存在 无则说明无右孩子
{
child++;
}
if (a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
/*child = parent;
parent = (child - 1) / 2;*///这样子是不行的 假设child为6 那么parent为2 与child为5是一样的
parent = child;
child = 2 * parent + 1;//假设为左孩子
}
else
{
break;
}
}
}
void HPInit(HP* php)
{
php->a = NULL;
php->size = php->capacity = 0;
}
void HPDestroy(HP* php)
{
free(php->a);
//free(php);
php->a = NULL;
php->size = php->capacity = 0;
}
void HPPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
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 HPPop(HP* php)
{
assert(php);
assert(php->size > 0);//保证堆里面有元素可以删除
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);//先让堆的数量size--,顺序不能颠倒,因为向下调整要用到size
}
HPDataType HPTop(HP* php)
{
assert(php);
assert(php->size > 0);//保证堆里面有元素能提取出来
return php->a[0];
}
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
test.c文件:
#include"Heap.h"
int main()
{
int a[] = { 4,2,8,1,5,6,9,7,3,2,23,55,232,66,222,33,7,1,66,3333,999 };
HP hp;
HPInit(&hp);
for (size_t i = 0; i < sizeof(a) / sizeof(int); i++)
{
HPPush(&hp, a[i]);
}
int i = 0;
while (!HPEmpty(&hp))
{
printf("%d ", HPTop(&hp));
//a[i++] = HPTop(&hp);
HPPop(&hp);
}
printf("\n");
//找出最大的前k个
/*int k = 0;
scanf("%d", &k);
while (k--)
{
printf("%d ", HPTop(&hp));
HPPop(&hp);
}
printf("\n");*/
HPDestroy(&hp);
return 0;
}
2.5分析实现堆主要函数
1.
void AdjustUp(HPDataType* a, int child)
{
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;
}
}
}
在数组的最后面插入一个数据,则需要找到它的父亲结点,然后一路顺藤摸瓜往上比较,在比较的过程中会发送交换位置(如果数据合适的话),直到从第一代到最后一代都符合小堆或者大堆的特点(大堆的最上面是最大的,小堆则相反)。child=parent是把数组下标就行互换(刚进来的数据成为了原来父亲的父亲结点),parent=(child-1)/2则是找到原来父亲的父亲结点的位置,就是找到新进去的数据的爷爷,在进行对比,一路比到原始祖先的位置。循环的条件child>0就是child达到最上面,最上面的上面是不存在的,所以以此作为循环终止的条件。
2.
在进行删除数据时,若把堆顶数据删除则会破坏掉原来的堆结构(比如是兄弟结点,祖先挂了,则其中一个孩子就会上去,从兄弟关系变成了父子关系),此时就需要重新进行堆排序,是比较浪费时间的操作,通过把堆顶的数据与最后的数据进行交换,再把最后的数据删除就可以保存原来堆的结构(不看头的话),只需要调整头到合适的位置即可,选出头的左右孩子中最小的一个,然后与其所以孩子就行比较,就可以更快速的删除堆顶数据并还原成堆的结构。
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = 2 * parent + 1;//假设为左孩子
//while (child<n&&child+1<n)这样子不行是因为child+1>n的话只能说明没有兄弟 child还是存在,还是可能比父亲小
while(child<n)
{
if (child+1<n&&a[child] > a[child + 1])//要在这里进行判断 child+1是否存在 无则说明无右孩子
{
child++;
}
if (a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
/*child = parent;
parent = (child - 1) / 2;*///这样子是不行的 假设child为6 那么parent为2 与child为5是一样的
parent = child;
child = 2 * parent + 1;//假设为左孩子
}
else
{
break;
}
}
}
3.
删除堆顶的元素就是通过交换首尾,在向下调整就行。
assert(php);
assert(php->size > 0);//保证堆里面有元素可以删除
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);//先让堆的数量size--,顺序不能颠倒,因为向下调整要用到size
4.
因为堆的实现是依靠顺序表来实现的,用动态顺序表,但空间满了就扩二倍空间,前提是原来有空间,否则就直接给四个对应类型的空间。最后加数据进去时,还需要对新数据进行堆排列。
void HPPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
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);//新放入堆的数据要排列
}
2.6用堆来进行排序
2.6.1小堆排序:
小堆的最上面是所有元素中最小的,所以把堆顶的元素与尾交换,再删除(不是真正的删除,只是把size减一,数据还是存在那里),一直删除,这样从头到尾就是由大到小的元素。
void HeapSort(int* a, int n)
{
// 降序,建小堆
// 升序,建大堆
/*for (int i = 1; i < n; i++)
{
AdjustUp(a, i);
}*/
/*for (int i = 1; i < n; i++)
{
AdjustUp(a, i);
}*/
for (int i = (n-1-1)/2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
void TestHeap2()
{
int a[] = { 4,2,8,1,5,6,9,7,2,7,9};
HeapSort(a, sizeof(a) / sizeof(int));
}
int main()
{
TestHeap2();
return 0;
}
从最有子结点开始组成小堆结构,然后通过往前移动,把每三个结点都组成小堆结构,一直到最上面,这样整个的结构就为小堆
2.6.2 大堆排序:
从祖先的下一个孩子开始比,越到后面比较次数就会增加,每次比较都会与此路径(都指向祖先的路径)的所有元素比较,直到最后一个结点,此时得到就是大堆结构,然后通过交换首尾元素,把最后一个元素删除,这样数组的元素就是从小到大的排序好。