堆和二叉树

一.二叉树

1.什么是树?

树是一种非线性的数据结构,它是由n个节点组成的一个具有层次关系的集合,它可以分为根节点和子树。例如:A是根节点,B以下的树是左子树,C以下的树是右子树,左子树和右子树也可以分成根节点和子树。

2.树的概念

节点的度:一个节点所含子树的个数是该节点的度;如上图:B的度是3

叶节点:度为0的节点;如上图:K,J,L,O,P都是叶子节点

分支节点:度不为0的节点

父节点:若一个节点有子节点,则这个节点就是子节点的父节点;如上图:A是B的父节点

子节点:一个节点具有子树的根节点,根节点称为该节点的子节点,如上图:B是A的子节点

兄弟节点:具有相同的父节点的节点为兄弟节点;如上图:D,E,F是兄弟节点

树的度:一颗树,所有节点中最大的度是树的度;如上图:树的度是3

节点的层:从根开始是第一层,根的子节点是第二层

树的高度:树中节点的最大层,如上图:树的高度是5

3.二叉树的概念

二叉树的度是二,所有节点最大的度不能超过二,节点的度可以是0也就是空树。

例如:A是根节点,B以下的树是左子树,C以下的树是右子树;左子树也可以分成根节点和子树,B是根节点,D以下的树是左子树,E以下的树是右子树;右子树也可以分成根节点和子树,C是根节点,F以下的树是左子树,G以下的树是右子树

4.特殊的二叉树
  1. 满二叉树: 一个二叉树,如果每一个层的结点数都达到最大数,这个树就是满二叉树。如果满二叉树的层数是k,那么节点数就是2^k-1(可以用等比数列公式计算,首项a1=1,等比q=2,项数n=k)
  2. 完全二叉树:假如一个满二叉树的层数是n,第n层的节点可以不满,但是每个节点要挨着。满二叉树是一种特殊的完全二叉树

 

5.二叉树的实现
1.顺序存储

顺序存储的前提是完全二叉树,顺序存储就是使用数组来存储,类似顺序表的结构,因为完全二叉树的每个节点都是紧紧挨着,不会有空间的浪费,而且会有特殊的性质。二叉树顺序存储在物理上是一个数组,在逻辑上是一棵二叉树。

parent为父节点,leftchild是父节点的左边子节点,rightchild是父节点的右边子节点

特殊性质:leftchild = parent * 2 + 1

                  rightchild = parent * 2 + 2

                  parent = (child - 1)  /  2        //child可以是leftchild或者rightchild

根据这个性质:知道parent的下标可以推算出leftchild和rightchild的下标,知道leftchild或者rightchild的下标可以推算出parent的下标,可以直接找到父节点和子节点的下标,但是前提这棵树是完全二叉树

2.链式存储

 二叉树的链式实现是指,用链表表示二叉树,其中链表的节点由数据域和左右指针域,数据域用于存储数据,指针域用于存储左右子节点的地址

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	BTDataType val;
}BTNode;


typedef BTNode* QlistData;
typedef struct QlistNode
{
	QlistData Data;
	struct QlistNode* next;

}QlistNode;
//每一个节点的类型

void QlistInit(Queue* pc)
//队列初始化
{
	assert(pc);
	pc->front = pc->rear = NULL;
	pc->size = 0;
}

void QlistPush(Queue* pc, QlistData x)
//入队列
{
	assert(pc);
	//这里-----------------------------------------------------
	QlistNode* NewNode = (QlistNode*)malloc(sizeof(QlistNode));
	if (NewNode == NULL)
	{
		perror("QlistPush");
		exit(-1);
	}
	NewNode->Data = x;
	NewNode->next = NULL;
	//从上到下其实是QlistBuy创建一个新的节点-------------------
	if (pc->rear == NULL)//此时队列为空
	{
		pc->rear = pc->front = NewNode;
	}
	else
	{
		pc->rear->next = NewNode;
		pc->rear = pc->rear->next;
	}
	++pc->size;
}

void QlistPop(Queue* pc)
//出队列
{
	assert(pc);
	assert(!QlistEmpty(pc));
	if (pc->rear == pc->front)
	{
		free(pc->front);
		pc->rear = pc->front = NULL;
	}
	else
	{
		QlistNode* cur = pc->front;
		pc->front = pc->front->next;
		free(cur);
		cur = NULL;
	}
	--pc->size;
}

bool QlistEmpty(Queue* pc)
//判断队列是否为空
{
	assert(pc);
	return pc->front == NULL;
}

QlistData QlistFront(Queue* pc)
//获取队首元素
{
	assert(pc);
	assert(!QlistEmpty(pc));

	return pc->front->Data;

}

QlistData QlistBack(Queue* pc)
//获取队尾元素
{
	assert(pc);
	assert(!QlistEmpty(pc));

	return pc->rear->Data;
}

int QlistSize(Queue* pc)
//获取有效元素的个数
{
	assert(pc);

	return pc->size;
}

void QlistDestory(Queue* pc)
//销毁节点
{
	assert(pc);
	QlistNode* cur = pc->front;
	while (pc->front)
	{
		pc->front = pc->front->next;
		free(cur);
		cur = pc->front;
	}
	//空间释放后指针置空
	pc->front = pc->rear = NULL;
	pc->size = 0;
}

void QlistPrint(Queue* pc)
//打印队列
{
	assert(pc);
	QlistNode* cur = pc->front;
	while (cur)
	{
		printf("%d ", cur->Data->val);
		cur = cur->next;
	}
	printf("\n");


}
//创建一个节点
BTNode* BuyBinaryTree(int x)
{
	BTNode* NewNode = (BTNode*)malloc(sizeof(BTNode));
	NewNode->left = NULL;
	NewNode->right = NULL;
	NewNode->val = x;
}
6.二叉树节点和高度问题

二叉树这类的问题可以用递归快速解决,因为二叉树的特殊性质,二叉树可以分成根节点和左右子树,左右子树也可以分成根节点和左右子树,这个过程和递归相似,所以使用递归解决很合适

递归重要的是限制条件(也就是什么时候递归返回)

1.求二叉树节点个数BinaryTreeSize()
  • 当节点为空节点开始返回,空节点不算一个节点,所以返回0
  • 当节点不为空的时候,返回左右子树的节点数加当前节点数(也就是1)return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1

int BinaryTreeSize(BTNode* root)

{

    if(root==NULL)

        return 0;

    else

        return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1
}

上面的代码还可以简化: 

int BinaryTreeSize(BTNode* root)
{
    return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

2 求二叉树叶子节点个数BinaryTreeLeafSize()

叶子节点就是左右子节点都是空节点

  • 当节点是空节点返回0
  • 当节点的左右子节点都是空节点返回1                                                                                       if (root->left == NULL && root->right == NULL)//叶子节点就是左右子树为空                             return 1;
  • 以上两种情况都不属于向下找

int BinaryTreeLeafSize(BTNode* root)
{
    if (root == NULL)
        return 0;
    if (root->left == NULL && root->right == NULL)//叶子节点就是左右子树为空
        return 1;

    return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

3.求二叉树第k层节点个数 BinaryTreeLevelKSize()

当前节点的第k层=左子树的第k-1层+右子树的第k-1层

  • 如果节点是空节点就返回0
  • 如果k==1返回1
  • 如果k!=1且节点不是空节点,就返回BinaryTreeLevelKSize(root->left, k - 1)+ BinaryTreeLevelKSize(root->right, k - 1)

int BinaryTreeLevelKSize(BTNode* root, int k)
{
    if (root == NULL)
        return 0;
    if (k == 1)
        return 1;

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

4. 二叉树查找值为x的节点BinaryTreeFind()

这个题如果找到节点了,要注意返回值的接收

  • 如果节点是空节点就返回NULL
  • 如果节点的值就是x,返回该节点
  • 如果该节点左子树函数调用的值不为0,则返回左子树函数调用的值
  • 如果该节点右子树函数调用的值不为0,则返回右子树函数调用的值
  • 以上情况都不符合说明没找到,返回NULL

//注意返回值的接收
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
    if (root == NULL)
        return NULL;

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

    BTNode* ret1 = BinaryTreeFind(root->left,x);
    if (ret1)
        return ret1;

    BTNode* ret2 = BinaryTreeFind(root->right,x);
    if (ret2)
        return ret2;

    return NULL;

}


二.堆  

1.什么是堆?

堆是一颗完全二叉树,堆分为大堆和小堆,父节点总是大于子节点的树是大堆,父节点总是小于子节点的树是小堆。 

 

堆是一个完全二叉树,所以可以用数组实现

2.向上调整建堆(建小堆)

比如:9,3,4,2,10,34,12,6要把这个数组建堆

向上调整建堆的关键:该元素的以上的元素都是堆,因为如果上面的元素都是堆,那么只要将该元素调整到正确位置,整个元素就是堆了

可以把数组第一个元素9看作堆的第一个元素,然后在堆底插入第二个元素3此时3以上的元素都是堆,然后判断要不要交换顺序。依次插入4,2,10,34,12,6重复以上步骤

这里是向上调整建堆的代码: 

  • 创建一个结构体表示堆,结构体成员size表示元素个数,capacity表示堆的元素容量,Data存放元素
  • AdjustUp()函数,通过子节点的下标kid找到父节点的下标int parent = (kid - 1) / 2,如果子节点比父节点小,那么交换节点且parent = (kid - 1) / 2,一直循环直到kid==0或子节点比父节点大
  • HeapInit()函数,为堆Data开辟空间,让堆的元素个数和最大容量等于n,pc->size = n,pc->capacity = n,把数组a拷贝到Data中。然后把第一个元素看作堆,从第二个元素开始向上调整
typedef int HpDataType;

typedef struct Heap
{
	int size;
	int capacity;
	int* Data;

}Heap;

//向上调整
//把下面的一个数向上提取
void AdjustUp(HpDataType* arr, int kid)//数组下标
{
	assert(arr);

	int parent = (kid - 1) / 2;
	while (kid != 0)
	{
		if (arr[kid] < arr[parent])
		{
			Swap(&arr[kid], &arr[parent]);
			kid = parent;
			parent = (kid - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//交换函数
void Swap(HpDataType* pa, HpDataType* pb)
{
	HpDataType tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}


//堆的初始化
void HeapInit(Heap* pc, HpDataType* a, int n)
{
	assert(pc);
	assert(a);

	Heap* tmp = (Heap*)malloc(sizeof(HpDataType) * n);
	if (tmp == NULL)
	{
		perror("malloc error");
		exit(-1);
	}
	pc->size = n;
	pc->capacity = n;
	pc->Data = tmp;
	memcpy(pc->Data, a, (sizeof(HpDataType) * n));

	//使用向上排序的前提是前面的数据是堆
	//这里相当于把第一个看做是堆中的数据,从第二个数据开始插入堆中向上排序
	int i = 0;
	for (i = 1; i < n; i++)
	{
		AdjustUp(pc->Data, i);
	}
	
}
3.向下调整建堆(建小堆)

比如:9,3,4,2,10,34,12,6要把这个数组建堆

向上调整建堆的关键:该元素的以下的元素都是堆,因为如果下面的元素都是堆,那么只要将该元素调整到正确位置,整个元素就是堆了

从第一个非叶子节点开始调整,非叶子节点以下节点可以看成是堆。然后从非叶子节点向下调整直到根节点

 这是向下调整的代码:

  • AdjustDown()函数,需要找出左右子节点中小的那一个kid, 先假设左孩子比较小kid = parent * 2 + 1,如果右孩子存在且比左孩子小,那么kid += 1,如果kidparent小就交换,kid = parent * 2 + 1,一直重复直到kid<nkidparent
  • HeapInit()函数,最后一个节点的下标是n-1,第一个非叶子节点((n-1)-1)/2,一直向下调整直到根节点
//向下调整(需要找出小孩子)
//向下调整的前提是下面的数据是堆
//把上面的一个数向下放,也就是同时对比两个下面的数取其一
void AdjustDown(HpDataType* arr,int parent, int n)//找到第一个叶子节点需要最后一个节点的下标
{
	int kid = parent * 2 + 1;
	while (kid < n)
	{
		if (kid+1<n&&arr[kid + 1] < arr[kid])
		{
			kid += 1;
		}
		if (arr[parent] > arr[kid])
		{
			Swap(&arr[parent], &arr[kid]);
			parent = kid;
			kid = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//交换函数
void Swap(HpDataType* pa, HpDataType* pb)
{
	HpDataType tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}


堆的初始化
void HeapInit(int* arr, int n)
{
	for (int i = (n - 2) / 2; i >= 0; i--)//n-1是下标
	{
		AdjustDown(arr, i, n);
	}


}
4.建堆的时间复杂度

向上调整建堆时间复杂度:O(Nlog2N) 

 向下调整建堆时间复杂度:O(N)

 5.堆的实现

堆的插入删除和检查是否为空的操作和顺序表的细节大致一样,这里不再展开                              顺序表的内容解析: ​​​​​​顺序表和链表

typedef int HpDataType;

typedef struct Heap
{
	int size;
	int capacity;
	int* Data;

}Heap;

//堆的初始化
void HeapInit(Heap* pc, HpDataType* a, int n)
{
	assert(pc);
	assert(a);

	Heap* tmp = (Heap*)malloc(sizeof(HpDataType) * n);
	if (tmp == NULL)
	{
		perror("malloc error");
		exit(-1);
	}
	pc->size = n;
	pc->capacity = n;
	pc->Data = tmp;
	memcpy(pc->Data, a, (sizeof(HpDataType) * n));

	//使用向上排序的前提是前面的数据是堆
	//这里相当于把第一个看做是堆中的数据,从第二个数据开始插入堆中向上排序
	int i = 0;
	for (i = 1; i < n; i++)
	{
		AdjustUp(pc->Data, i);
	}
	
}

//堆的销毁
void HeapDestory(Heap* pc)
{
	assert(pc);

	free(pc->Data);
	pc->Data = NULL;
	pc->size = pc->capacity = 0;
}


//堆的打印
void HeapPrint(Heap* pc)
{
	int i = 0;
	for (i = 0; i < pc->size; i++)
	{
		printf("%d ", pc->Data[i]);
	}
	printf("\n");
}


//向上调整
//把下面的一个数向上提取
void AdjustUp(HpDataType* arr, int kid)//数组下标
{
	assert(arr);

	int parent = (kid - 1) / 2;
	while (kid != 0)
	{
		if (arr[kid] < arr[parent])
		{
			Swap(&arr[kid], &arr[parent]);
			kid = parent;
			parent = (kid - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//堆的插入
void HeapPush(Heap* pc, HpDataType x)
{
	assert(pc);

	if (pc->size == pc->capacity)
	{
		int NewCapacity = pc->capacity == 0 ? 4 : pc->capacity * 4;
		Heap* tmp = (Heap*)realloc(pc->Data, sizeof(HpDataType) * NewCapacity);
		if (tmp == NULL)
		{
			perror("realloc error");
			exit(-1);
		}
		pc->Data = tmp;
	}
	pc->Data[pc->size] = x;
	pc->size++;
	AdjustUp(pc->Data, pc->size-1);
}

//交换函数
void Swap(HpDataType* pa, HpDataType* pb)
{
	HpDataType tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}


//向下调整(需要找出小孩子)
//向下调整的前提是下面的数据是堆
//把上面的一个数向下放,也就是同时对比两个下面的数取其一
void AdjustDown(HpDataType* arr,int parent, int n)//找到第一个叶子节点需要最后一个节点的下标
{
	int kid = parent * 2 + 1;
	while (kid < n)
	{
		if (kid+1<n&&arr[kid + 1] < arr[kid])
		{
			kid += 1;
		}
		if (arr[parent] > arr[kid])
		{
			Swap(&arr[parent], &arr[kid]);
			parent = kid;
			kid = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//堆的删除
//此处让最后一个数据覆盖堆顶的数据,再向下排序
void HeapPop(Heap* pc)
{
	assert(pc);
	assert(pc->size > 0);

	Swap(&pc->Data[0], &pc->Data[pc->size - 1]);
	pc->size--;

	AdjustDown(pc->Data,0,pc->size);
	
}



//判断堆是否为空
int HeapEmpty(Heap* pc)
{
	assert(pc);

	return pc->size == 0;
}

//堆的数据个数
int HeapSize(Heap* pc)
{
	assert(pc);

	return pc->size;
}

//获取栈顶数据
HpDataType HeapTop(Heap* pc)
{
	assert(pc);
	assert(!HeapEmpty(pc));

	return pc->Data[0];
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值