数据结构--------------二叉树

1.树

1.1 树的结构与概念

树是一种非线性结构的,他是由n(n>0)个结点组成一个具有层次关系的集合。把他叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,⽽叶朝下的。如图数据结构--------------二叉树_第1张图片

树形结构中,字树不能由交集,否则就不是树形结构

如图下

数据结构--------------二叉树_第2张图片

1:子树是不相交的

2:除了根结点,每个结点都有一个父结点

3:一个N结点的树有N-1个边

1.2树相关术语

数据结构--------------二叉树_第3张图片

父节点/双亲结点:若一个结点有子结点,这个节点就称为其子结点的父结点如图A就是B结点的父节点

子结点/孩子结点:一个结点含有的子树根结点称为子结点:如图B就是A的子节点

结点的度:一个结点有几个子结点就度就为多少 如图A 的度为6 E的度为2 K德度为0;

树的度:一棵树中,最大结点的度称为树的度如图树结点就为6

叶⼦结点/终端结点:度为 0 的结点称为叶结点; 如上图: B C H I... 等结点为叶结点
分⽀结点/⾮终端结点:度不为 0 的结点; 如上图: D E F G... 等结点为分⽀结点
兄弟结点:具有相同父元素的结点称为兄弟结点例如上图 B C D E F G就是兄弟结点
结点的层次:从跟结点开始,根为第一层,根的子结点为第二曾
树的高度和深度:树中结点最大的层次如图:树的高度为4
2. 二叉树
2.1概念与结构
在树形结构中,我们最常⽤的就是⼆叉树,⼀棵⼆叉树是结点的⼀个有限集合,该集合由⼀个根结点 加上两棵别称为左⼦树和右⼦树的⼆叉树组成或者为空。
数据结构--------------二叉树_第4张图片
从上面图可以看出来二叉树不存在大于二的结点而且二叉树有左右之分而且不能颠倒,因此二叉树是有序树
注意:对于任意的二叉树都是由以下几种情况复合而成的
数据结构--------------二叉树_第5张图片
2.2 特殊的二叉树
如果一个的二叉树每个结点都是2,那么这个二叉树就是满二叉树,也就是说一个二叉树的层次为k,那么结点总数就是 2 k − 1,如果符合这个条件那么他就是满二叉树
数据结构--------------二叉树_第6张图片
2.2.2 完全二叉树
完全二叉树是由满二叉树引出来的,要注意满二叉树是一种特殊的完全二叉树,而且完全二叉树要遵守先左后右的规则
数据结构--------------二叉树_第7张图片
如图 图一就是一个完全二叉树,而图二不是一个完全二叉树
二叉树的性质
(1)若规定根节点的层数为1,则一颗非空的完全二叉树的第i层上最多有2 i−1 个结点
(2)若规定根结点的层数为 1 ,则深度为 h 的⼆叉树的最⼤结点数是 2 h − 1
(3)若规定根结点的层数为 1 ,具有 n 个结点的满⼆叉树的深度 h = log2 (n + 1) ( log
以2为底, n+1 为对数)
(4). 若 i>0 , i 位置结点的双亲序号: (i-1)/2 ; i=0 , i 为根结点编号,⽆双亲结点
(5). 若 2i+1=n 否则⽆左孩⼦
(6). 若 2i+2=n 否则⽆右孩⼦
2.3:二叉树的存储结构
⼆叉树⼀般可以使⽤两种结构存储,⼀种顺序结构,⼀种链式结构。
首先我们来实现顺序结构存储来实现二叉树
typedef int HeDataType;

typedef struct Heap {
	HeDataType* arr;
	int size;
	int capacity;
}Hp;

这个定义其实是和顺序表一样的因为我们要用顺序表来实现二叉树

//初始化

void initia(Hp* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

和顺序表初始化时一样的首先我们要将这个数组指向空然后让里面的有效数据和大小都为0;

//销毁

void hpdestory(Hp* ps)
{
	if (ps->arr)
		free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

这个也跟顺序表一样的不懂得可以去看一下我前面的顺序表首先如果数组不为空就直接free然后记住要让他指向空然后容量和大小都变成0

//堆插入
 

//堆插入
void inserthp(Hp* ps,HeDataType x)
{
	assert(ps);
	if (ps->capacity == ps->size)
	{
		HeDataType newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		HeDataType* tmp = (HeDataType*)realloc(ps->arr, sizeof(HeDataType)* newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
	ps->arr[ps->size] = x;
	Adjustup(ps->arr,ps->size);
	ps->size++;
}

先插入之前我们还是老样子判断是否为空如果为空的情况下我们就要申请空间根顺序表的申请空间操作时一样的然后我们要分析一下二叉树我们是要建一个大堆还是小堆,如果建一个大堆我们的根节点是最值而且每个根节点的值要大于子结点,建小堆则相反所以我们要另外写一个函数来实现如下向上调整

//调整

//调整
void Adjustup(HeDataType* arr, int  child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

第一个结点的时候就是根节点,后面陆续插入的时候如果我们要建大堆就需要判断根结点和子结点谁比较大大的放到前面所以我们要写一个交换的函数Swap这个函数就相当简单在这就不写了,然后让父结点走到子结点的位置,根据二叉树的性质可得知当子节点等于i的时候那么他的父节点就是(i-1)/2,反之我们知道父节点就可以求出子节点子节点就是2i+1,让父节点大于子节点那就没有必要交换所以直接break;

//堆删除

void pophp(Hp* ps)
{
	assert(!emptyh(ps));
	Swap(&ps->arr[0], &ps->arr[ps->size-1]);
	ps->size--;
	Adjustdown(ps->arr, 0, ps->size);
}

我们删除要删除数据如果直接删除第一个数据再去调整的话这样的代价是很大的,所以我们可以首先可以让第一个数据和最后一个交换然后直接让收效数据减减这样就可以删除数据然后再去因为根的数变了所以我们需要向下调整所以就要另外写一个函数向下调整如下

//向下调整

void Adjustdown(HeDataType *a,int parent,int x)
{
	int child = parent * 2 + 1;
	while (child a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

因为我们要从头开始向下调整我们拿到的是头结点的位置我们要首先判断左右结点那个比较大然后再去根头结点比较如果子节点大于父结点就叫交换然后让父节点走到子结点的位置然后子结点进行向下走。

//判断是否为空

//判断是否为空
bool emptyh(Hp* ps)
{
	assert(ps);
	return ps->size == 0;
}

判断是否为空就只需要判断里面的有效数据。

//取堆顶

HeDataType hptop(Hp* ps)
{
	assert(!emptyh(ps));
	return ps->arr[0];
}

取堆顶就只需要取到数组第一个数

下面是总体代码分为三个部分

Heap.h

#pragma once
#include
#include
#include
#include

typedef int HeDataType;

typedef struct Heap {
	HeDataType* arr;
	int size;
	int capacity;
}Hp;

void initia(Hp* ps);
void hpdestory(Hp* ps);
//堆插入
void inserthp(Hp* ps,HeDataType x);
//堆删除
void pophp(Hp* ps);
//判断是否为空
bool emptyh(Hp* ps);
//取堆顶
HeDataType hptop(Hp* ps);

Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
//初始化
void initia(Hp* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}
//销毁
void hpdestory(Hp* ps)
{
	if (ps->arr)
		free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
//判断是否为空
bool emptyh(Hp* ps)
{
	assert(ps);
	return ps->size == 0;
}
//交换
void Swap(int *x,int *y)
{
	int n = *x;
	*x = *y;
	*y = n;
}
//调整
void Adjustup(HeDataType* arr, int  child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
//堆插入
void inserthp(Hp* ps,HeDataType x)
{
	assert(ps);
	if (ps->capacity == ps->size)
	{
		HeDataType newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		HeDataType* tmp = (HeDataType*)realloc(ps->arr, sizeof(HeDataType)* newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
	ps->arr[ps->size] = x;
	Adjustup(ps->arr,ps->size);
	ps->size++;
}
void Adjustdown(HeDataType *a,int parent,int x)
{
	int child = parent * 2 + 1;
	while (child a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//堆删除
void pophp(Hp* ps)
{
	assert(!emptyh(ps));
	Swap(&ps->arr[0], &ps->arr[ps->size-1]);
	ps->size--;
	Adjustdown(ps->arr, 0, ps->size);
}
//取堆顶
HeDataType hptop(Hp* ps)
{
	assert(!emptyh(ps));
	return ps->arr[0];
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"

void test()
{
	Hp hp;
	initia(&hp);
	inserthp(&hp, 4);
	inserthp(&hp, 3);
	inserthp(&hp, 6);
	inserthp(&hp, 8);
	pophp(&hp);
	pophp(&hp);
	pophp(&hp);
	pophp(&hp);
	while (!emptyh(&hp))
	{
		printf("%d ", hptop(&hp));
		pophp(&hp);
	}
	hpdestory(&hp);
}
int main()
{
	int arr[] = { 23,1,4,56,43,60 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	test();
	/*test1(arr,sz);*/
	
	phsort(arr, sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

如何用链表实现请关注下一期内容

你可能感兴趣的:(数据结构)