二叉树的顺序结构

目录

一、二叉树的顺序结构及实现

1.1 二叉树的顺序结构

1.2 堆的概念及结构

1.3 堆的实现

1.3.1 堆向上调整算法和向下调整算法

向下调整算法代码

向上调整算法代码

1.3.2 堆的创建

1.3.3 堆的删除

1.3.4  堆的代码实现

test.c

Heap.c

Heap.h

1.4 堆的应用

1.4.1 堆排序

堆排序代码

1.4.2 TOP-K问题

TOP-K问题代码


一、二叉树的顺序结构及实现

1.1 二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

1.2 堆的概念及结构

1、每个节点都大于其子节点的二叉树结构叫做大堆,也叫大根堆

2、每个节点都小于其子节点的二叉树结构叫做小堆,也叫小根堆

二叉树的顺序结构_第1张图片

1.3 堆的实现

1.3.1 堆向上调整算法和向下调整算法

1、向下调整算法

现在我们给出一个数组,逻辑上把这个数组看作是一个二叉树结构,我们通过根节点开始的向下调整算法,将这个二叉树调整成小堆(也可以是大堆,这里我们以小堆为例)。

但要注意的是,向下调整算法的一个前提是:左右子树必须都是小堆。

向下调整算法思路:所以假设父亲节点的左右子节点都是小堆(空节点既可以看成是小堆,也可以看成是大堆),先比较出左右子节点较小的那个,然后和父亲节点进行比较。若父亲节点小,则不交换,否则交换。交换了之后父亲节点来到了子节点的位置,然后重复上面的步骤,直到父亲节点比子节点小,或者子节点为空。

二叉树的顺序结构_第2张图片

 上图显示了向下调整的步骤和过程 

向下调整算法代码

//传入的参数是,a表示数组;n,表示数组的元素个数
//parent,表示要调整的父亲节点(数组中的下标)
void AdjustDown(int* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child])
		{
			child++;
		}
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

向上调整算法和向下调整算法的思路很相似,向上调整算法要求除开该节点其他节点必须符合小堆特点,这样才可以将这个节点调整到正确的位置上。

向上调整算法思路:从子节点入手,让这个子节点和父亲节点进行比较,若子节点较大,则不用交换;若子节点较小,则交换。此时子节点就被交换到了父节点的位置,称为新的子节点。重复上述步骤,就可以完成向上调整。

二叉树的顺序结构_第3张图片

向上调整算法代码

//a,表示的是数组;child,是要调整的那个子节点(数组中的下标)
void AdjustUp(int* a, int child)
{
	assert(a);
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			int tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

1.3.2 堆的创建

在上面两个算法的基础之上,我们就可以利用这两个算法建立小堆。

向上调整建立小堆:从二叉树的第一个节点开始向上调整,直到最后一个数,这样所创建的二叉树就是一个小堆。

	//方法一  以向上调整的方式建立小堆
	for (int i = n; i > 0; i--)
	{
		AdjustUp(a, i-1);
	}

向下调整建立小堆:因为叶子节点是不需要向下调整的,所以我们从最后一个元素的父节点开始向下调整。这样一来,同样可以创建出小堆 

//方法二  以向下调整的方式建立小堆
for (int i = n - 1; i > 0; i--)
{
    AdjustDown(a, n, (i - 1) / 2);
}

建堆的时间复杂度为O(N)。(这个需要注意一下,因为在后面的排序当中,会涉及到建堆的时间复杂度)

1.3.3 堆的删除

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

二叉树的顺序结构_第4张图片

1.3.4  堆的代码实现

test.c

#include "Heap.h"
int main()
{
	int a[] = { 70, 56, 30, 25, 15, 10, 75 };
	Heap hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
	{
		HeapPush(&hp, a[i]);
	}
	HeapPrint(&hp);
	printf("%d\n", HeapTop(&hp));
	HeapPop(&hp);
	HeapPrint(&hp);

	HeapPop(&hp);
	HeapPrint(&hp);

	HeapPop(&hp);
	HeapPrint(&hp);

	HeapPop(&hp);
	HeapPrint(&hp);

	HeapDestory(&hp);
	return 0;
}

Heap.c

#include "Heap.h"
void Swap(HPDataType* px, HPDataType* py)
{
	HPDataType tmp = *px;
	*px = *py;
	*py = tmp;
}
void AdjustDown(int* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && 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(int* a, int child)
{
	assert(a);
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			int tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
// 堆的构建
void HeapInit(Heap* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->capacity = hp->size = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->a);
	hp->capacity = hp->size = 0;
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x){
	assert(hp);
	if (hp->size == hp->capacity)
	{
		int NewCapacity = hp->capacity == 0 ? 4 : 2 * hp->capacity;
		HPDataType* NewHeap = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * NewCapacity);
		if (NewHeap == NULL)
		{
			perror("malloc failed!");
			return;
		}
		hp->a = NewHeap;
		hp->capacity = NewCapacity;
	}
	hp->a[hp->size] = x;
	AdjustUp(hp->a, hp->size);
	hp->size++;
}
// 堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	assert(!HeapEmpty(hp));
	//交换数据
	Swap(&hp->a[0], &hp->a[hp->size - 1]);
	hp->size--;
	AdjustDown(hp->a, hp->size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}
// 堆的判空
//堆为空,则返回1  否则返回0
int HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->size == 0;
}
void HeapPrint(Heap* hp)
{
	assert(hp);
	int i = 0;
	for (i = 0; i < hp->size; i++)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}

Heap.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 
#include 
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}Heap;
//向下调整
void AdjustDown(int* a, int n, int parent);
//向上调整
void AdjustUp(int* a, int child);
// 堆的构建
void HeapInit(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);
void HeapPrint(Heap* hp);
void Swap(HPDataType* px, HPDataType* py);

1.4 堆的应用

1.4.1 堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1、建堆

升序:建大堆

降序:建小堆

2、利用堆删除思想来进行排序

二叉树的顺序结构_第5张图片

假如我想把一个数组排成升序,那么我就要建大堆。将原数组利用向上调整或者是向下调整的方式建立成大堆。因为是大堆,数组中第一个元素一定是最大的,接下来将这个最大的元素换到数组的最后一个位置上去,然后除开最后一个元素,重复上述步骤,建立大堆,把最大的元素换到倒数第二的位置上去,以此类推,这样就能够得到升序的数组了。

堆排序代码

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 
//总结:对于TOPK问题,最大的前k个数,要建小堆
//                    最小的前k个数,要建大堆
// 对于排序来说
// 排升序,要建大堆
// 排降序,要建小堆
// 对数组进行堆排序
void Swap(HPDataType* px, HPDataType* py)
{
	HPDataType tmp = *px;
	*px = *py;
	*py = tmp;
}
void print(int* a,int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
}
void AdjustDown(int* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && 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(int* a, int child)
{
	assert(a);
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] > a[parent])
		{
			int tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int* a, int n)
{
	//以排降序为例
	//方法一  以向上调整的方式建立小堆
	//for (int i = n; i > 0; i--)
	//{
	//	AdjustUp(a, i-1);
	//}
	//print(a, n);
	//方法二  以向下调整的方式建立小堆
	for (int i = n - 1; i > 0; i--)
	{
		AdjustDown(a, n, (i - 1) / 2);
	}
	printf("排序前:");
	print(a, n);
	printf("\n");
	//然后开始排序
	for (int end = n - 1; end > 0; end--)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
	}
	printf("排序后:");
	print(a, n);
}
int main()
{
	int a[] = { 5,46,4,6,4,51,2,3,1,49,8,7,93,12,3 };
	int n = sizeof(a) / sizeof(a[0]);
	HeapSort(a, n);
	return 0;
}

排序结果如下

二叉树的顺序结构_第6张图片

1.4.2 TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

1、用数据集合中前K个元素来建堆

前k个最大的元素,则建小堆

前k个最小的元素,则建大堆

2、以前k个最大的元素为例,用剩余的N-K个元素依次与堆顶元素来比较,若比堆顶元素小,则比较下一个;若比堆顶元素大,则替换堆顶元素,并向下调整。

TOP-K问题代码

#include 
#include 
#include 
#include 
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}Heap;
void HeapPrint(Heap* hp)
{
	assert(hp);
	int i = 0;
	for (i = 0; i < hp->size; i++)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}
// 堆的构建
void HeapInit(Heap* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->capacity = hp->size = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->a);
	hp->capacity = hp->size = 0;
}
//获得堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->a[0];
}
//向下调整
void AdjustDown(int* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && 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(int* a, int child)
{
	assert(a);
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			int tmp = a[child];
			a[child] = a[parent];
			a[parent] = tmp;
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
//数据的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	if (hp->size == hp->capacity)
	{
		int NewCapacity = hp->capacity == 0 ? 4 : 2 * hp->capacity;
		HPDataType* NewHeap = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * NewCapacity);
		if (NewHeap == NULL)
		{
			perror("malloc failed!");
			return;
		}
		hp->a = NewHeap;
		hp->capacity = NewCapacity;
	}
	hp->a[hp->size] = x;
	AdjustUp(hp->a, hp->size);
	hp->size++;
}
void PrintTopK(int* a, int n, int k)
{
	Heap hp;
	HeapInit(&hp);
	for (int i = 0; i < k; i++)
	{
		HeapPush(&hp, a[i]);
	}
	for (int i = k; i < n; i++)
	{
		if (HeapTop(&hp) < a[i])
		{
			hp.a[0] = a[i];
			AdjustDown(hp.a, hp.size, 0);
		}
	}
	HeapPrint(&hp);
	HeapDestory(&hp);
}
int main()
{
    int n = 1000000;
	int* a = (int*)malloc(sizeof(int) * n);
	srand(time(0));
	for (size_t i = 0; i < n; ++i)
	{
		a[i] = rand() % 1000000;
	}
	// 再去设置10个比100w大的数
	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[5355] = 1000000 + 3;
	a[51] = 1000000 + 4;
	a[15] = 1000000 + 5;
	a[2335] = 1000000 + 6;
	a[9999] = 1000000 + 7;
	a[76] = 1000000 + 8;
	a[423] = 1000000 + 9;
	a[3144] = 1000000 + 10;
	PrintTopK(a, n, 10);
	return 0;
}

你可能感兴趣的:(数据结构,算法,c++)