《数据结构》二叉树--C语言

1.树

1.1树的概念

     树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

根结点,根节点没有前驱结点。

除根节点外,其余结点被分成是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。

树是递归定义的。

 2.二叉树

2.1概念和结构

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

注意:1. 二叉树不存在度大于2的结点。
           2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。

5种基本形态:

2.2特殊二叉树

1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。

                      一个二叉树的层数为K,且结点总数是 (2^k)- 1,则它就是满二叉树。

2. 完全二叉树:完全二叉树和二叉树相似,完全二叉树的最后一层不是满的,(从左到右,且连续,这里的n1不是1就是0)。


 

2.3二叉树的性质

1. 若规定根节点的层数为1,则一棵非空二叉树的i层上最多有 2^(i-1)个结点 
2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 (2^h)- 1
3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为n2 ,则有 n0= n2+1

4.Parent=child-1/ 2leftchild=parent *2+1rightchild=parent *2+2

2.4二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。

完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,

这里的堆和操作系统,虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

2.5堆

2.5.1堆的概念及结构

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

性质:1.堆分大根堆和小根堆

             大根堆:任何父亲 >= 孩子

             小根堆:任何父亲 <= 孩子

          2.堆总是一棵完全二叉树

2.5.2向上调整算法:

向上调整算法有一个前提:左右子树必须是一个堆,才能调整

思路:把插入的数和根进行比较,小的话就交换。

void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while(child > 0)
	{
		if (a[child] > a[parent])如果孩子大于父亲就交换
		{
			Swap(&a[child], &a[parent]);//交换孩子和父亲
			
			//更新孩子和父亲的位置
			child = parent;
			parent = (child - 1) / 2;
		}
		else//如果孩子小于父亲,则不做调整,break退出循环.
		{
			break;
		}
	}
}

向上调整算法的时间复杂度为 :O(NlogN);

2.5.3向下调整算法:

现在我们给出一个数组,逻辑上看做一颗完全二叉树。通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整

思路以小堆为例,选择左右child中小的那一个与 parent 比大小,小的上去大的下来

          1.找到左右子树中小的一边,接下来的交换只在小的一边。

          2.根据所建的堆,变换判定条件


void AdjustDown(HPDataType* a, int n, int parent)
{
	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;
		}
	}
}

向下调整算法的时间复杂度为  :O(N)

2.5.4堆的构建

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Hp;

void Swap(HPDataType* p1, HPDataType* p2);
void HeapPrint(Hp* php);
//堆的初始化
void HeapInit(Hp* php);
//堆的销毁
void HeapDestroy(Hp* php);
//入数据
void HeapPush(Hp* php, HPDataType x);
//出数据
void HeapPop(Hp* php);
//栈顶数据
HPDataType HeapTop(Hp* php);
//判空
bool HeapEmpty(Hp* php);






void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void HeapPrint(Hp* php)
{
	for (int i = 0; i < php->_size; i++)
	{
		printf("%d", php->_a[i]);
	}
	printf("\n");
}

//堆的初始化
void HeapInit(Hp* php)
{
	assert(php);

	php->_a = NULL;
	php->_capacity = php->_size = 0;
}

//堆的销毁
void HeapDestroy(Hp* php)
{
	assert(php);

	free(php);
	php->_a = NULL;
	php->_capacity = php->_size = 0;
}

//入数据
void HeapPush(Hp* php, HPDataType x)
{
	assert(php);
	//判断开空间
	if (php->_capacity == php->_size)
	{
		int newcapacity = php->_capacity == 0 ? 4 : php->_capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->_a,sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}

		php->_capacity = newcapacity;
		php->_a = tmp;
    }
    //入数据
	php->_a[php->_size] = x;
	php->_size++;

	AdjustDown(php->_a, php->_size,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);
}
//栈顶数据
HPDataType HeapTop(Hp* php)
{
	assert(php);
	assert(php->_size > 0);

	return php->_a[0];
}
//判空
bool HeapEmpty(Hp* php)
{
	assert(php);

	return php->_size == 0;
}

2.5.5堆排序

思路:利用向下调整将原有数组变成一个大根堆,再交换最后一个元素和首元素,这样最大的。次大的都出来了

原数组建堆:找到最后一个叶子节点(n-1),从它的父亲开始调整(n-1-1)/2,从下往上开始。

//升序建大根堆
void AdjustDown(int* a, int n, int parent)
{
	//假设左孩子为大的
	int child = parent * 2 + 1;
	while (child < n)
	{
		//验证猜测并且判断右孩子是否存在
		if (child+1 < n && a[child + 1] > a[child])   child++;
		if (a[parent] < a[child])
		{
			//把小的往下放
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}

void HeapSort(int* arr, int n)
{
	//循环从后面往前面对需要的数组元素使用向下调整算法
	int i = 0;
	for (i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}
	//用来控制向下调整时的数组元素个数
	int end = n - 1;
	while (end > 0)
	{
		//交换元素,获得最大值
		Swap(&arr[0], &arr[end]);
		//进行向下调整, 因为开始end为n-1嘛
		AdjustDown(arr, end, 0);
		//去除较大值
		--end;
	}
}


int main()
{
	//待排序的数组
	int arr[] = { 6,4,2,8,3,1,9,7,5,0 };
	//调用主体函数
	HeapSort(arr, sizeof(arr) / sizeof(arr[0]));
	//打印数据
	PrintArray(arr, sizeof(arr) / sizeof(arr[0]));

	return 0;
}

2.6二叉树的遍历

2.6.1前序.中序.后序遍历

前序遍历:根---左子树---右子树

递归展开图:

 结果:A  B  D  E  C

代码实现:

void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL");
		return;
	}
	printf("%d ", root->date);
	PreOrder(root->left);
	PreOrder(root->right);
}

中序遍历:左子树---根---右子树

递归展开图:

 结果:D  B  E  A  C

代码实现:

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->date);
	InOrder(root->right);
}

后序遍历:左子树---右子树---根

递归展开图:

结果:D  E  B  C  A

代码实现:

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->date);
}

2.6.2层序遍历

思路就是:出队列的时候,左右子树入队列

图解:

代码实现:

void BinaryTreeLevelOrder(BTNode* root)
{
	Queue qu;
	BTNode * cur;
 
	QueueInit(&qu);
 
	QueuePush(&qu, root);  
 
	while (!QueueIsEmpty(&qu))  //队列不为空,重复出队列,入队列
	{
		cur = QueueTop(&qu);
 
		putchar(cur->_data);
 
		if (cur->_left)    //入孩子
		{
			QueuePush(&qu, cur->_left);
		}
 
		if (cur->_right)
		{
			QueuePush(&qu, cur->_right);
		}
 
		QueuePop(&qu);   //头删,出队列
	}
 
	QueueDestory(&qu);
}

 

多多支持,感谢感谢!!!!

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值