数据结构与算法:二叉树

树概念及结构

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

注意:树形结构中,子树之间不能有交集,否则就不是树形结构

节点的度 :一个节点含有的子树的个数称为该节点的度; 如上图: A 的为 6
叶节点或终端节点 :度为 0 的节点称为叶节点; 如上图: B C H I... 等节点为叶节点
非终端节点或分支节点 :度不为 0 的节点; 如上图: D E F G... 等节点为分支节点
双亲节点或父节点 :若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图: A B 的父节点
孩子节点或子节点 :一个节点含有的子树的根节点称为该节点的子节点; 如上图: B A 的孩子节点
兄弟节点 :具有相同父节点的节点互称为兄弟节点; 如上图: B C 是兄弟节点
树的度 :一棵树中,最大的节点的度称为树的度; 如上图:树的度为 6
节点的层次 :从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推;
树的高度或深度 :树中节点的最大层次; 如上图:树的高度为 4
堂兄弟节点 :双亲在同一层的节点互为堂兄弟;如上图: H I 互为兄弟节点
节点的祖先 :从根到该节点所经分支上的所有节点;如上图: A 是所有节点的祖先
子孙 :以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是 A 的子孙
森林 :由 m m>0 )棵互不相交的树的集合称为森林

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了, 既然保存值域,也要保存结点和结点之间 的关系 ,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。我们这里就简单的了解其中最常用的 孩子兄弟表示法

其他方法:

二叉树概念及结构

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

1. 二叉树不存在度大于2的结点
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
注意:对于任意的二叉树都是由以下几种情况复合而成的:

满二叉树和完全二叉树

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

满二叉树:每一层都是满的

完全二叉树:前h-1层是满的,最后一层不一定满,但从左到右必须连续

假设树的高度:h

满二叉树结点总数:2^0+2^1+2^2+.......+2^(h-1) = 2^h - 1

完全二叉树结点总数:

前h-1层:2^0+2^1+2^2+.......+2^(h-2) = 2^(h-1)-1

第h层:最少 1个,最多 2^(h-1)个

所以总数属于  [2^(h-1),2^h -1]                                    

假设满二叉树树结点总数为N个

假设完全二叉树树结点总数为N个

二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
二叉树逻辑结构是非线性的,是树性的,物理存储结构分为顺序和链式结构。
1. 顺序存储
顺序结构存储就是使用 数组来存储 ,一般使用数组 只适合表示完全二叉树 ,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺 序存储在物理上是一个数组,在逻辑上是一颗二叉树。
满/完全 二叉树
父子结点间下标的规律
leftchild  = parent * 2 + 1
rightchild= parent * 2 + 2
parent = (child-1)/2  不分左右孩子
2.链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。
通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面课程学到高阶数据结构如红黑树等会用到三叉链。

堆的概念及结构

小堆:任意一个父亲<=孩子

大堆:任意一个父亲>=孩子

存储的数组不一定有序,有序肯定是堆,无序可能是堆

堆是完全二叉树的顺序存储结构且必须满足父子数据大小关系有序

堆的创建

Heap.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int HPDataType;

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

void HeapInit(HP* php);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);
// 规定删除堆顶(根节点)
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
size_t HeapSize(HP* php);
bool HeapEmpty(HP* php);

Heap.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 = php->capacity = 0;
}

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustUp(HPDataType* 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;
			//child = (child - 1) / 2;
			//parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

// O(logN)
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}

		php->a = tmp;
		php->capacity = newCapacity;
	}

	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}


void AdjustDown(int* a, int size, int parent)
{
	int child = parent * 2 + 1;

	while (child < size)
	{
		// 假设左孩子小,如果解设错了,更新一下

		if (child + 1 < size && a[child + 1] < a[child])
		{
			++child;
		}

		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

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);
}

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}

size_t HeapSize(HP* php)
{
	assert(php);

	return php->size;
}

bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

text.c

#include"Heap.h"
void menu() {
	printf("********************堆**********************\n");
	printf("******1、入堆          2、删堆**************\n");
	printf("******3、打印堆顶元素***********************\n");
	printf("******4、堆的数据个数***********************\n");
	printf("******5、堆的判空***************************\n");
	printf("******6、打印堆的所有元素*******************\n");
}
int main()
{
	HP hp;
	HeapInit(&hp);
	int op ;
	HPDataType x;
	do
	{   
		menu();
		printf("请选择您的操作:\n");
		scanf("%d", &op);
		switch (op)
		{
		case 1:
			printf("请输入堆的数据:\n");
			scanf("%d", &x);
			HeapPush(&hp,x);
			break;
		case 2:
			HeapPop(&hp);
			break;

		case 3:
			printf("%d\n", HeapTop(&hp));
			break;
		case 4:
			printf("%d\n", HeapSize(&hp));
			break;
		case 5:
			if (HeapEmpty(&hp))
				printf("堆为空\n");
			else
				printf("堆不为空\n");
			break;
		case 6:
			while (!HeapEmpty(&hp))
				{
					printf("%d ", HeapTop(&hp));
					HeapPop(&hp);
				}
			printf("\n");
			break;
		case 0:
			printf("goodby\n");
			break;
		default:
			printf("您的输入有误,请重新输入\n");
			break;
		}
	} while (op);

	HeapDestroy(&hp);
	return 0;
}

 这里我们建立了小堆,如果需要大堆,只需要改变几个小于符号即可

堆的应用  

堆排序
TOP-K问题

堆排序

我们可以将一窜数据排成大堆或者小堆,然后间接地将其转换成有序的小堆或大堆

进而实现堆排序

Heap.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<time.h>

typedef int HPDataType;

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

void HeapInit(HP* php);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);
// 规定删除堆顶(根节点)
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
size_t HeapSize(HP* php);
bool HeapEmpty(HP* php);

void Swap(HPDataType* p1, HPDataType* p2);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int size, int parent);

Heap.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 = php->capacity = 0;
}

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

 
void AdjustUp(HPDataType* 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;
			//child = (child - 1) / 2;
			//parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

// O(logN)
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}

		php->a = tmp;
		php->capacity = newCapacity;
	}

	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}



void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;

	while (child < size)
	{
		// 假设左孩子大,如果解设错了,更新一下

		if (child + 1 < size && a[child + 1] > a[child])
		{
			++child;
		}

		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

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);
}

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}

size_t HeapSize(HP* php)
{
	assert(php);

	return php->size;
}

bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

 text.c

#include"Heap.h"
// 升序
void HeapSort(int* a, int n)
{
	// 建大堆
	// O(N*logN)
	/*for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}*/

	// O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}
    
    //O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

int main()
{
	int a[] = { 4, 6, 2, 1, 5, 8, 2, 9 };

	HeapSort(a, sizeof(a)/sizeof(int));

	for (int i = 0; i < sizeof(a)/sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

这里我们将数组中的数据进行堆排序,排成大堆{9,8,6,5,4,2,2,1}

之后间接地排成有序的小堆{1,2,2,4,5,6,8,9}

进而实现堆排序

TOP-K问题

TOP-K 问题:即求数据结合中前 K 个最大的元素或者最小的元素,一般情况下数据量都比较大
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前 K 个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<time.h>
typedef int HPDataType;

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;

	while (child < size)
	{
		// 假设左孩子小,如果解设错了,更新一下

		if (child + 1 < size && a[child + 1] < a[child])
		{
			++child;
		}

		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void AdjustUp(HPDataType* 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;
			//child = (child - 1) / 2;
			//parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void CreateNDate()
{
	// 造数据
	int n = 10000000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (int i = 0; i < n; ++i)
	{
		int x = (rand() + i) % 10000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

void PrintTopK(const char* file, int k)
{
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}

	// 建一个k个数小堆
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		perror("malloc error");
		return;
	}

	// 读取前k个,建小堆
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);
		AdjustUp(minheap, i);
	}

	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)
	{
		if (x > minheap[0])
		{
			minheap[0] = x;
			AdjustDown(minheap, k, 0);
		}
	}

	for (int i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}
	printf("\n");

	free(minheap);
	fclose(fout);
}

int main()
{
	CreateNDate();
	PrintTopK("Data.txt", 5);

	return 0;
}

这里创建了一个文件data.txt,并在其中生成10000000个数据

然后建立k个数据的小堆,接着往下读取,比堆顶元素大的则替换,然后继续维系小堆

直到所有元素都进行过筛选比较,此时的小堆剩余的k个元素就是最大的前k个元素

复杂度的讨论

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果)

向下调整建堆的复杂度

利用AdjustDown建堆的复杂度

向下调整建堆的累积调整次数是T(h)      一层节点的个数*向下调整的次数

  T(h) = 2^(h-2)*1 + 2^(h-3)*2 + ...... + 2^1*(h-2) + 2^0*(h-1)

2T(h) = 2^(h-1)*1 + 2^(h-2)*2 + ...... + 2^2*(h-2) + 2^1*(h-1)

错位相减

  T(h) = 2^(h-1) + 2^(h-2) + 2^(h-3) + ...... + 2^1 + 2^0 - h = 2^h -1 -h

设N是数结点总数,满二叉树高度h与N的关系

将其带入:

因此: 向下建堆的时间复杂度为O(N)

向上调整建堆的复杂度

利用AdjustUp建堆的复杂度

向上调整建堆的累积调整次数是T(h)      一层节点的个数*向上调整的次数

  T(h) = 2^1*1 + 2^2*2 + ...... + 2^(h-2)*(h-2) + 2^(h-1)*(h-1)

2T(h) = 2^2*1 + 2^3*2 + ...... + 2^(h-1)*(h-2) + 2^(h)*(h-1)

错位相减

T(h) = -(2^0 + 2^1 + 2^2 + 2^3 + ...... + 2^(h-1)) + 2^h^(h-1) +2^0

  = 2^h*(h-1) - 2^h + 2 = 2^h*(h-2) +2

 设N是数结点总数,满二叉树高度h与N的关系

将其带入:

因此:向上建堆的时间复杂度为O(NlogN)

注意

向下调整建堆AdjustDown是从最下面(最后一个或者是倒数第二行第一个)开始往前调整的

向上调整建堆AdjustUp是从最上面(堆顶或第二行第一个)开始往后调整的

二叉树链式结构的实现

二叉树是:
1. 空树
2. 非空:根节点,根节点的左子树、根节点的右子树组成的

节点1是树的根节点,节点2是左子树的根节点,结点4是右子树的根节点

每一棵非空树都有根节点

二叉树的遍历

二叉树的遍历有: 前序 / 中序 / 后序的递归结构遍历
1. 前序遍历(Preorder Traversal亦称先序遍历)访问根结点的操作发生在遍历其左右子树之前 根--左子树--右子树
2. 中序遍历(Inorder Traversal)访问根结点的操作发生在遍历其左右子树之中(间)
    左子树--根--右子树
3. 后序遍历(Postorder Traversal)访问根结点的操作发生在遍历其左右子树之后
    左子树--右子树--根
4.层序遍历: 设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

前序遍历:

中序遍历:

后序遍历:

层序遍历:

由此可见:遍历顺序是从局部到整体都要满足

相关理解:(例如前序)

广度优先遍历、深度优先遍历

一、深度优先遍历

深度优先遍历相当于二叉树的x序遍历。

所谓深度优先搜索,是从图中的一个顶点出发,每次遍历当前访问顶点的临界点,一直到访问的顶点没有未被访问过的临界点为止。然后采用依次回退的方式,查看来的路上每一个顶点是否有其它未被访问的临界点。访问完成后,判断图中的顶点是否已经全部遍历完成,如果没有,以未访问的顶点为起始点,重复上述过程。

假如有上述的无向图,采用深度优先算法遍历这个图的过程为:

1. 首先任意找一个未被遍历过的顶点,例如从V1开始,由于V1率先访问过了,所以,需要标记V1的状态为访问过;

2. 然后遍历V1的邻接点,例如访问V2,并做标记,然后访问V2的邻接点,例如V4(做标记),然后V8,然后V5;

3. 当继续遍历V5的邻接点时,根据之前做的标记显示,所有邻接点都被访问过了。此时,从V5回退到V8 ,看V8是否有未被访问过的邻接点,如果没有,继续回退到V4,V2 ,V1;

4. 通过查看V1,找到一个未被访问过的顶点V3,继续遍历,然后访问V3 邻接点V6,然后V7;

5. 由于V7 没有未被访问的邻接点,所有回退到V6 ,继续回退至V3 ,最后到达V1 ,发现没有未被访问的;

6. 最后一步需要判断是否所有顶点都被访问,如果还有没被访问的,以未被访问的顶点为第一个顶点,继续依照上边的方式进行遍历。

经过上述流程,我们可以得到上述的图的深度优先遍历结果:

V1 -> V2 -> V4 -> V8 -> V5 -> V3 -> V6 -> V7

二、广度优先遍历

广度优先遍历则相当于二叉树的层次遍历。指从图中的某一顶点出发,遍历每一个顶点时,依次遍历其所有的邻接点,然后再从这些邻接点出发,同样依次访问它们的邻接点。按照此过程,直到图中所有被访问过的顶点的邻接点都被访问到。最后还需要做的操作就是查看图中是否存在尚未被访问的顶点,若有,则以该顶点为起始点,重复上述遍历的过程。

仍然以这张图为例子:

假设V1作为起始点,遍历其所有的邻接点V2和V3,以V2为起始点,访问邻接点V4和V5,以V3为起始点,访问邻接点V6、V7,以V4为起始点访问V8 ,以 V5为起始点,由于 V5 所有的起始点已经全部被访问,所有直接略过,V6 和 V7也是如此。以V1为起始点的遍历过程结束后,判断图中是否还有未被访问的点,由于图1中没有了,所以整个图遍历结束。遍历顶点的顺序为:

经过上述流程,我们可以得到上述的图的深度优先遍历结果:

V1 -> V2 -> v3 -> V4 -> V5 -> V6 -> V7 -> V8

深度优先遍历: 前序遍历中序遍历后序遍历

广度优先遍历: 层序遍历

二叉树的各项操作

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<math.h>
#include<stdbool.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}TreeNode;

//引用队列-------------------------------------------------------------------------------

typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
		QDataType val;
		struct QueueNode* next;
}QNode;
typedef struct Queue
{
		QNode * phead;
		QNode * ptail;
		int size;
}Queue;
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}

	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);

	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}

	newnode->val = x;
	newnode->next = NULL;

	if (pq->ptail == NULL)
	{
		pq->ptail = pq->phead = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}

	pq->size++;
}
void QueuePop(Queue* pq)
{
	assert(pq);
	 
	assert(pq->phead);

	QNode* del = pq->phead;
	pq->phead = pq->phead->next;
	free(del);
	del = NULL;

	if (pq->phead == NULL)
		pq->ptail = NULL;

	pq->size--;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->phead == NULL;
}
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	 
	assert(pq->phead);

	return pq->phead->val;
}
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

//---------------------------------------------------------------------------------------

TreeNode* BuyTreeNode(int x)
{
	TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
	assert(node);

	node->data = x;
	node->left = NULL;
	node->right = NULL;

	return node;
}

TreeNode* CreateTree()
{
	TreeNode* node1 = BuyTreeNode(1);
	TreeNode* node2 = BuyTreeNode(2);
	TreeNode* node3 = BuyTreeNode(3);
	TreeNode* node4 = BuyTreeNode(4);
	TreeNode* node5 = BuyTreeNode(5);
	TreeNode* node6 = BuyTreeNode(6);
	


	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;

	return node1;
}

//前序
void PrevOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	printf("%d ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

//中序
void InOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

//后序
void PostOrder(TreeNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	PostOrder(root->left);
	PostOrder(root->right);
    printf("%d ", root->data);
}

//二叉树节点总个数
int TreeSize(TreeNode* root)
{
	return root == NULL ? 0 :
		TreeSize(root->left) +
		TreeSize(root->right) + 1;
}

//叶子节点的个数
int TreeLeafSize(TreeNode* root)
{
	// 空 返回0
	if (root == NULL)
		return 0;
	// 不是空,是叶子 返回1
	if (root->left == NULL
		&& root->right == NULL)
		return 1;

	// 不是空 也不是叶子  分治=左右子树叶子之和
	return TreeLeafSize(root->left) +
		TreeLeafSize(root->right);
}

//二叉树的高度
int TreeHeight(TreeNode* root)
{
	if (root == NULL)
		return 0;
	
	return TreeHeight(root->left) > TreeHeight(root->right) ? TreeHeight(root->left) + 1 :TreeHeight(root->right)  + 1;
	//return fmax(TreeHeight(root->left), TreeHeight(root->right)) + 1;

/*	if (root == NULL)
		return 0;
	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; */
}

//第k层的节点个数
int TreeLevelK(TreeNode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
		return 0;

	if (k == 1)
		return 1;

	return TreeLevelK(root->left, k - 1)
		+ TreeLevelK(root->right, k - 1);
}

// 二叉树查找值为x的节点
TreeNode* TreeFind(TreeNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;

	if (root->data == x)
		return root;

	TreeNode* ret1 = TreeFind(root->left, x);
	if (ret1)
		return ret1;

	TreeNode* ret2 = TreeFind(root->right, x);
	if (ret2)
		return ret2;

	return NULL;

  
 /* if (root == NULL)
		return NULL;

	if (root->data == x)
		return root;

	TreeNode* ret1 = TreeFind(root->left, x);
	if (ret1)
		return ret1;

	return TreeFind(root->right, x); */
}

//bool TreeFind(TreeNode* root, BTDataType x)
//{
//	if (root == NULL)
//		return NULL;
//
//	if (root->data == x)
//		return root;
//
//	return TreeFind(root->left, x) || TreeFind(root->right, x);
//}

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
TreeNode* TreeCreate(char* a,int* pi)
{
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}

	TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
	if (root == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	root->data = a[(*pi)++];

	root->left = TreeCreate(a, pi);
	root->right = TreeCreate(a,pi);

	return root;
}

//层序
void LevelOrder(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	int levelSize = 1;
	while (!QueueEmpty(&q))
	{
		// 一层一层出
		while (levelSize--)
		{
			TreeNode* front = QueueFront(&q);
			QueuePop(&q);

			printf("%d ", front->data);

			if (front->left)
				QueuePush(&q, front->left);

			if (front->right)
				QueuePush(&q, front->right);
		}
		printf("\n");

		levelSize = QueueSize(&q);
	}

	QueueDestroy(&q);
}

// 判断二叉树是否是完全二叉树
bool TreeComplete(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front == NULL)
			break;

		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
    }
    // 前面遇到空以后,后面还有非空就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;
}

//销毁二叉树
void DestroyTree(TreeNode* root)
{
	if (root == NULL)
		return;

	DestroyTree(root->left);
	DestroyTree(root->right);
	free(root);
}

例题精讲

单值二叉树

如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。

只有给定的树是单值二叉树时,才返回 true;否则返回 false

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
bool isUnivalTree(struct TreeNode* root) {
    if(root == NULL)
      return true;

    if(root->left && root->left->val != root->val)
      return false;

    if(root->right && root->right->val != root->val)
      return false;

    return isUnivalTree(root->left) && isUnivalTree(root->right);
}

相同的树

给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    if(p==NULL && q==NULL)
      return true;

    if(p==NULL || q==NULL)
      return false;

    if(p->val != q->val)
      return false;

    return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
 
}

二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
 int TreeSize(struct TreeNode* root)
{
	return root == NULL ? 0 :
		TreeSize(root->left) +
		TreeSize(root->right) + 1;
}
void preorder(struct TreeNode* root,int* a,int* pi)
{
  if(root==NULL)
    return; 

  a[(*pi)++] = root->val;
  preorder(root->left,a,pi);
  preorder(root->right,a,pi);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    int n = TreeSize(root);
    int* a= (int*)malloc(sizeof(int)*n);
    *returnSize = n;
    
    int i=0;
    preorder(root,a,&i);

    return a;
}

对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
bool _isSymmetric(struct TreeNode* root1,struct TreeNode* root2)
{
     if(root1 == NULL && root2 == NULL)
       return true;

     if(root1 == NULL || root2 == NULL)
       return false;

     if(root1->val != root2->val)
       return false;
       
     return _isSymmetric(root1->left,root2->right) && _isSymmetric(root1->right,root2->left); 

}
bool isSymmetric(struct TreeNode* root) {
    return _isSymmetric(root->left,root->right);
}

 另一棵树的子树

给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    if(p==NULL && q==NULL)
      return true;

    if(p==NULL || q==NULL)
      return false;

    if(p->val != q->val)
      return false;

    return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
}
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
     if(root == NULL)
       return false;

     if(root->val == subRoot->val)
     {
       if(isSameTree(root,subRoot))
       return true;
     }
     return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);

/*  if(root == NULL)
       return false;

     return isSameTree(root,subRoot) || isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot); */

}

翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
struct TreeNode* invertTree(struct TreeNode* root) {
    if (root == NULL) {
        return NULL;
    }
    struct TreeNode* left = invertTree(root->left);
    struct TreeNode* right = invertTree(root->right);
    root->left = right;
    root->right = left;
    return root;
}

平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

int height(struct TreeNode* root) {
    if (root == NULL) {
        return 0;
    } else {
        return fmax(height(root->left), height(root->right)) + 1;
    }
}

bool isBalanced(struct TreeNode* root) {
    if (root == NULL) {
        return true;
    } else {
        return fabs(height(root->left) - height(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);
    }
}

选择题

解:

(1)  由于对任何一棵二叉树, 度为0的分支结点个数 比 度为2的叶结点个数多1个

叶子结点是度为0的叶结点个数,所以是199+1=200个

(2)  选A

(3) 设叶子结点个数为x,度为2的叶结点个数为x-1,度为1的个数为1或0

      2x-1+1/0 =2n

由于2n是偶数,x是整数,所以度为1的结点个数为1,x=n。

(4) 根据高度与结点个数的公式

      [2^(h-1),2^h -1]     

当h=10,531属于这个区间,所以高度为10

(5) 

设叶子结点个数为x,度为2的叶结点个数为x-1,度为1的个数为1或0

      2x-1+1/0 =767

      767是奇数,x是整数,所以度为1的结点个数为0

      x=384

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

你好,赵志伟

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

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

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

打赏作者

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

抵扣说明:

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

余额充值