(数据结构初阶)二叉树 (一)

本文详细探讨了树和二叉树的基础概念,包括节点度、层次结构、特殊节点类型,以及二叉树的性质如满二叉树和完全二叉树。还介绍了堆的数据结构,包括大根堆和小根堆的性质,堆排序算法,以及堆的实现和应用实例,如TOPK问题的解决方案。
摘要由CSDN通过智能技术生成

目录

一   树的概念与结构

二   二叉树的概念与结构

1  二叉树的概念:

2  二叉树的性质:

3  树的存储结构

三 堆

1 堆的基本性质

2 向下调整算法

3 建堆

4 堆排序  O(N*logN)

5 堆的实现(封装)

6 TOPK问题


一   树的概念与结构

1  树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。有一个特殊的结点,称为根结点,根节点没有前驱结点,除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继,因此,树是递归定义的

2    节点的度:一个节点含有的子树的个数称为该节点的度。

3    叶节点或终端节点:度为0的节点称为叶节点。

4    非终端节点或分支节点:度不为0的节点。

5    兄弟节点:具有相同父节点的节点互称为兄弟节点。

6    树的度:一棵树中,最大的节点的度称为树的度。

7    节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。

8     树的高度或深度: 树中节点的最大层次。
9    堂兄弟节点:双亲在同一层的节点互为堂兄弟。
10  节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
11  子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
12  森林 :由m(m>0)棵互不相交的树的集合称为森林;

二   二叉树的概念与结构

1  二叉树的概念:

1.1  一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

1.2  每个结点最多有两棵子树,即二叉树不存在度大于2的结点。 二叉树的子树有左右之分,其子树的次序不能颠倒。

1.3  满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说, 如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
1.4  完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为 K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 n 的结点一一对应时称之为完全二叉树。 要注意的是 满二叉树是一种特殊的完全二叉树。

2  二叉树的性质:

2.1   若规定根节点的层数为 1 ,则一棵非空二叉树的 第i层上最多有2^(i-1) 个结点。
2.2.   若规定根节点的层数为 1 ,则 深度为h的二叉树的最大结点数是2^h- 1
2.3   对任何一棵二叉树 , 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1。
2.4   若规定根节点的层数为 1 ,具有 n个结点的满二叉树的深度,h=Log2(n+1)。  (ps Log2(n+1) log 2 为 底,n+1 为对数 )

3  树的存储结构

3.1  顺序结构存储就是使用 数组来存储 一般使用数组只适合表示完全二叉树 ,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储。 二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
3.2   链式存储:二叉树的链式存储结构是指, 用链表来表示一棵二叉树 ,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,初阶数据结构一般都是二叉链,高阶数据结构 如红黑树等会用到三叉链

三 堆

1 堆的基本性质

1.1 堆分为大根堆和小根堆两种。 大根堆:父亲节点大于等于孩子节点; 小根堆:父亲节点小于等于孩子节点。
1.2 物理结构为数组,逻辑结构为完全二叉树。
1.3 父亲节点下标为 parent,则左孩子节点下标就为: parent*2+1,右孩子节点下标为: parent*2+2

2 向下调整算法

2.1 特点:左子树与右子树恰好是小堆时可以使用向下调整算法建立小堆。

2.2 向下调整算法:①选出左右孩子中小的那个树。②小的孩子与父亲比较,a、如果小的孩子比父亲小,则跟父亲交换,并且把原来孩子的位置当成父亲,继续向下调整,直到走到叶子节点。b、如果小的孩子比父亲大,则不需要继续调整,整个树已经是小堆。

2.3 代码实现

//交换函数
void Swap(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}
//当左右子树都为大堆时,可以使用向下调整算法将整颗树调整为大堆
//向下调整算法
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		//找出左右孩子中大的那一个(建立小堆只需要找到孩子中小的那个)
		if (child+1<n && a[child] < a[child + 1])
		{
			child++;
		}
		//如果左右孩子中大的那一个比父节点大就交换(建小堆改为比父亲小就交换)
		if (a[child] > a[parent])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

3 建堆

3.1 左右子树不是小堆,随意给一个数组时,从倒数第一个非叶子节点,从后往前,按编号,依次作为子树去向下调整。

3.1.1 建堆代码实现

	//建堆(时间复杂度O(N)。)
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

4 堆排序  O(N*logN)

4.1 排升序建立大堆,第一个为最大的数,让它与最后一个数交换,将最后一个节点除去,不打乱堆顺序,只需要向下调整即可。

4.1.1 排升序代码实现

//堆排序,排升序要建大堆( 不会打乱堆顺序 )
void HeapSort(int* a, int n)
{
	//建堆(时间复杂度O(N)。)
	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]);
		//选出次大的(顺序没乱,向下调整即可),时间复杂度O(logN)。
		AdjustDown(a, end, 0);
		end--;
	}
}

4.2 排降序只需将堆建成小堆即可,每次选出最小的数放到最后。

5 堆的实现(封装)

5.1 Heap.h


#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

typedef int HPDateType;

struct Heap
{
	HPDateType* a;
	int size;
	int capacity;
};
typedef struct Heap  HP;

//  HP* HeapInit(HPDateType* a, int n);
void Swap(HPDateType* a, HPDateType* b);
void AdjustDown(HPDateType* a, int n, int parent);
void AdjustUp(HPDateType* a, int child);
void HeapInit(HP* php, HPDateType* a, int n);
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 HeapPrintf(HP* php);

5.2 Heap.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Heap.h"

//交换函数
void Swap(HPDateType* a, HPDateType* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

//向下调整算法
void AdjustDown(HPDateType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		//找出左右孩子中大的那一个(建立小堆只需要找到孩子中小的那个)
		if (child + 1 < n && a[child] < a[child + 1])
		{
			child++;
		}
		//如果左右孩子中大的那一个比父节点大就交换(建小堆改为比父亲小就交换)
		if (a[child] > a[parent])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//向上调整算法
void AdjustUp(HPDateType* a, int child)
{
	int parent = (child-1-1)/2 ;
	while (child >0)
	{
		if (a[child] > a[parent])
		{
			Swap(&a[parent], &a[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}


//初始化堆
void HeapInit(HP* php, HPDateType* a, int n)
{
	assert(php);
	php->a = (HPDateType*)malloc(sizeof(HPDateType) * n);
	if (php->a == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	memcpy(php->a, a, sizeof(HPDateType) * n);
	php->capacity = n;
	php->size = n;

	//建堆
	for (int i = (php->size - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, php->size,i);
	}

}

//销毁堆
void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->capacity = php->size = 0;

}

//插入数据
void HeapPush(HP* php, HPDateType x)
{
	assert(php);
	//满了需要扩容
	if (php->size == php->capacity)
	{
		HPDateType* tmp = (HPDateType*)realloc(php->a, sizeof(HPDateType) * php->capacity * 2);
		if (tmp == NULL)
		{
			printf("relloc fail\n");
			exit(-1);
		}
		php->a = tmp;
		php->capacity *= 2;
	}
	php->a[php->size] = x;
	php->size++;
	//向上调整算法
	AdjustUp(php->a,php->size-1);
}

//删除堆顶数据
void HeapPop(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);
}

HPDateType HeapTop(HP* php) 
{
	assert(php);
	assert(php->size>0);
	return php->a[0];
}

int HeapSize(HP* php) 
{
	assert(php);
	return php->size;
}

bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size==0;
}

//打印堆
void HeapPrintf(HP* php)
{
	for (int i = 0; i < php->size; i++)
	{
		printf("%d  ", php->a[i]);
	}
	printf("\n\n");
	int num = 0;
	int leveSize = 1;
	for (int i = 0; i < php->size; i++)
	{
		printf("%d  ", php->a[i]);
		++num;
		if (num == leveSize)
		{
			printf("\n");
			num = 0;
			leveSize *= 2;
		}
	}
	printf("\n\n");
}
//

5.3 main.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Heap.h"


int main()
{
	int a[] = {1,58,45,79,16,25,48,33,15,88};
	int n = sizeof(a) / sizeof(a[0]);

	HP hp;
	HeapInit(&hp, a, n);
	HeapPrintf(&hp);

	//插入
	HeapPush(&hp, 99);
	HeapPrintf(&hp);
	HeapPush(&hp, 8);
	HeapPrintf(&hp);

    //删除
	HeapPop(&hp);
	HeapPrintf(&hp);
	HeapPop(&hp);
	HeapPrintf(&hp);

	HeapDestroy(&hp);
	return 0;
}

6 TOPK问题

int* getLeastNumbers(int* arr, int arrSize, int k, int* returnSize){
    if(k==0)
    {
        * returnSize=0;
        return NULL;
    }
    int* array=(int *)malloc(sizeof(int)*k);
    //前K个数建立大堆
    for(int i=0;i<k;i++)
    {
        array[i]=arr[i];
    }
    for(int j=(k-1-1)/2;j>=0;j--)
    {
        AdjustDown(array,k,j);        
    }
    //剩下的N-K个数,只要比堆顶的数据小,就替换堆顶,并向下调整。
    for(int i=k;i<arrSize;i++)
    {
        if(array[0]>arr[i])
        {
            array[0]=arr[i];
            AdjustDown(array,k,0); 
        }
    }
    *returnSize=k;
    return array;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zhang丶&|!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值