二叉树(前篇)

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 大堆排序:

从祖先的下一个孩子开始比,越到后面比较次数就会增加,每次比较都会与此路径(都指向祖先的路径)的所有元素比较,直到最后一个结点,此时得到就是大堆结构,然后通过交换首尾元素,把最后一个元素删除,这样数组的元素就是从小到大的排序好。

  • 59
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值