二叉树(一)

一、树

  • 节点的度:一个节点含有的字数的个数就叫做该节点的度
  • 叶节点/终端节点:度为0的节点
  • 非终端节点/分支接点:度不为0的几点
  • 双亲结点/父节点:含有子节点的节点
  • 孩子节点/子节点:一个节点含有的子树的节点
  • 兄弟节点:具有相同父节点的节点
  • 树的度:一棵树中,最大的节点称为数的度
  • 节点的层次:从根节点开始,根为第一层,根的子节点为第二层,以此类推
  • 树的高度或深度:树的最大层次
  • 堂兄弟节点:父节点在同一层的节点
  • 节点的祖先:从根节点到该节点所经过的所有节点
  • 子孙:以某节点为根的子树中任意节点都称为该节点的子孙
  • 森林:m颗互不相交的数的集合

树的表示方法:

 在上述代码中,firstChild指向左边的第一个子节点,而pNextBro指向右边的第二个子节点,若没有子节点,则pNextBro指向NULL。如下图所示:

二、二叉树 

概念

如上图所示,二叉树不存在度大于2的节点,且二叉树的子树有左右之分,分别为左子树和右子树。二叉树还有以下几种情况:

有两种特殊的二叉树:

  • 满二叉树:该二叉树中的每层的节点数都达到最大值;或者说如二叉树的层数为k,且总结点数的和为2^k - 1;
  • 完全二叉树:前k-1层是满的,最后一层是不满的;

二叉树还具有以下几种性质:

  • 若根节点的层数为1,则第i层上最多有2^( i - 1 )个节点
  • 若根节点的层数为1,则深度为h的二叉树的最大节点数是2^h - 1
  • 对任意一个二叉树,如果度为0的节点数为a,则度为2的节点数为a-1;
  • 若根结点的的层数为1,具有n个节点的满二叉树的深度h = log(n+1);
  • 在完全二叉树中,度为0的节点个数比度为1的节点个数多一;度为1的节点数为1或0;
  • 对于有n个节点的完全二叉树,按从上至下,从左至右的数组顺序对所有节点从0开始编号:

在上图中,若父节点的下标是2,对应的值是12,则我们可以得到其左孩子下标为 2 i + 1 = 5,则对应的值是25;右孩子下标为 2 i + 2 = 6,对应的值是10;据图可以看出是这样的。

如果说父节点的下标是3,对应的值是35,左孩子下标为7,对应的值是21,右孩子下标是8而下标8数据不存在,故不存在右孩子。

通过父节点 i 下标推子节点下标,n表示节点总数:

  • 右节点下标:2i+2 < n;
  • 左孩子下标:2i+1 < n;

通过子节点下标推父节点下标:

  • 右节点下标R为偶数:(R - 1) / 2或者(R - 2) / 2;
  • 左节点下标L为奇数:(L - 1) / 2。

在上图中,10是右节点对应下标6,无论下标减1或是2,其都能得到父节点下标;25是左节点,下标减1得到父节点下标。

实现 

对于二叉树不选择数组而是链式存储的原因:

对于完全二叉树选择数组是没问题的,而对于非完全二叉树来说,数组有空间的浪费。

三、堆的实现 

堆分为大堆(父节点比子节点大)和小堆(父节点比子节点小)。堆有两个性质:

  • 堆中某个节点的值总是不大于(或不小于)其父节点的值;
  • 堆总是一颗完全二叉树;

堆有以下接口:

typedef int HPDataType;
typedef struct Heap {
	HPDataType* arr;
	int size;//堆的数据个数
	int capacity;//堆的容量
}HP;

//堆的初始化
void HeapInit(HP* hp);

//堆的销毁
void HeapDestory(HP* hp);

//堆的插入
void HeapPush(HP* hp, HPDataType val);

//堆的数据删除
void HeapPop(HP* hp);

//获取堆顶数据
int HeapTop(HP* hp);

//打印
void HeapPrint(HP* hp);

//判空
int HeapEmpty(HP* hp);

//堆的数据个数
int HeapSize(HP* hp);
//堆的初始化
void HeapInit(HP* hp) {
	assert(hp);
	hp->arr = NULL;
	hp->capacity = hp->size = 0;
}

//堆的销毁
void HeapDestory(HP* hp) {
	assert(hp);
	free(hp->arr);
	hp->arr = NULL;
	hp->capacity = hp->size = 0;
}

//交换数据
void Swap(HPDataType* x1, HPDataType* x2) {
	HPDataType tmp = *x2;
	*x2 = *x1;
	*x1 = tmp;
}

//向上调整算法
void AdjustUp(HP* hp, int child) {
	assert(hp);

	//这里的child和parent的下标关系是:parent * 2 + 1 = child;
	int parent = (child - 1) / 2;
	while (child > 0) {

		//当前堆是小堆,就是说子节点比父节点大,父节点比子节点小
		//	这里的if条件判断就是说:child比parent小,子字节比父节点小,因此交换
		if (hp->arr[child] < hp->arr[parent]) {
			//交换两值
			Swap(&hp->arr[child], &hp->arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else {
			break;
		}
	}
}

//向下调整算法
void AdjustDown(HPDataType* arr, int root, int size) {
	assert(arr);

	int child = root * 2 + 1;
	while (child < size) {

		//1、找到左右孩子的较小值
		if (arr[child] > arr[child + 1]) {
			child++;
		}
		//2、交换
		if (child+1 < size && arr[child] < arr[root]) {
			Swap(&arr[child], &arr[root]);
			root = child;
			child = root * 2 + 1;
		}
		else {
			break;
		}
	}
}
//堆的插入
void HeapPush(HP* hp, HPDataType val) {
	assert(hp);
	//检查容量
	if (hp->size == hp->capacity) {
		int newCapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		HPDataType* newArr = realloc(hp->arr, sizeof(HPDataType) * newCapacity);
		if (newArr == NULL) {
			printf("realloc failed\n");
			exit(-1);
		}
		hp->capacity = newCapacity;
		hp->arr = newArr;
	}

	//插入数据
	hp->arr[hp->size] = val;
	hp->size++;

	//向上调整算法
	AdjustUp(hp, hp->size - 1);
}

//堆的删除:删除堆顶的数据
void Delete(HP* hp) {
	assert(php);
	Swap(&php->_arr[0], &php->_arr[php->_size - 1]);
	php->_size--;
	AdjustDown(php->_arr, php->_size, 0);
}

//获取堆顶数据
int HeapTop(HP* hp) {
	assert(hp);
	assert(hp->size > 0);
	return hp->arr[0];

}

//打印
void HeapPrint(HP* hp) {
	assert(hp);
	for (int i = 0; i < hp->size; i++){
		printf("%d ", hp->arr[i]);
	}
	printf("\n");
}

//判空
int HeapEmpty(HP* hp) {
	assert(hp);
	return hp->size == 0;
}

//堆的数据个数
int HeapSize(HP* hp) {
	assert(hp);
	return hp->size;
}

堆排序 

将无序数组排成有序数组

  1. 将数组建成堆
  2. 对堆进行排序

建堆既可以使用向上调整算法,也可以使用向下调整算法:

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

//向下调整算法
void AdjustDown(int* arr, int size, int root) {
	int parentPos = root;
	int childPos = parentPos * 2 + 1;
	while (childPos < size) {
		if (childPos+1<size && arr[childPos] < arr[childPos + 1]) {
			childPos++;
		}
		if (arr[parentPos] < arr[childPos]) {
			Swap(&arr[parentPos], &arr[childPos]);
			parentPos = childPos;
			childPos = parentPos * 2 + 1;
		}
		else {
			break;
		}
	}
}

void HeapSort(int* arr, int size) {
    
    //向上调整算法:
	for (int i = 0; i < size; i++){
		AdjustUp(arr, i);
	}

    //1、数组转换成堆
    //向下调整算法:从最后一个非叶子节点(最后一个节点的父亲)向前遍历
    //这里的i表示要调整的节点下标,size表示数组大小。
	int end = (size - 1 - 1) / 2;
	for (int i = end; i >= 0; i--) {
		AdjustDown(arr, size, i);
	}

    //2、数组堆排序:
	int end = size - 1;
	while (end > 0) {
		Swap(&arr[0], &arr[end]);
		AdjustDown(arr, end--, 0);
	}
}

 在数组转换成大堆后,将首元素与尾元素交换,此时数组末尾元素就是最大的数据,再通过向下调整算法将数据个数减一(即end--,因为此时末尾数据是最大的,我们接下来就是找到次大数据并将其归位),从下标为0的位置开始调整。以此类推即可将数组排序。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值