C语言笔记-续 •数据结构--树结构 (二叉树)•

数据结构-树结构

1.

1.1概念

     树是一种 非线性 的数据结构,它是由 n n>=0 )个有限结点组成一个具有层次关系的集合。 把它 叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的形状。
(1)有一个特殊的结点,称为根结点,根节点没有前驱结点。
(2)除根节点外,其余结点被分成M(M>0) 个互不相交的集合 T1 T2 …… Tm ,其中每一个集合Ti(1<= i <= m) 又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0 个或多个后继。
(3)因此,树是递归定义的。
下面几幅图片,形象的介绍一下树结构:
1.2树的一些名词
根据这棵树结构说一下:
•节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A 的为 6
•叶节点或终端节点:度为0 的节点称为叶节点; 如上图: B C H I... 等节点为叶节点
•非终端节点或分支节点:度不为0 的节点; 如上图: D E F G... 等节点为分支节点
•双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A B的父节点
•孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B A 的孩子节点
•兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B C 是兄弟节点
•树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
•节点的层次:从根开始定义起,根为第1 层,根的子节点为第 2 层,以此类推;
•树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
•节点的祖先:从根到该节点所经分支上的所有节点;如上图:A 是所有节点的祖先
•子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图所有节点都是A 的子孙
•森林:由m m>0 )棵互不相交的多颗树的集合称为森林
1.3树的表示
       树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,实际中树有很多种表示方式, 如:双亲表示法,孩子表示法、孩子兄弟表示法等等。这里就简单的了解其中最常用的孩子 兄弟表示法(左兄弟和右兄弟)
typedef int DataType;
struct Node
{
    struct Node* _firstChild1;    // 第一个孩子结点(左兄弟)
    struct Node* _pNextBrother;   // 指向其下一个兄弟结点(右兄弟)
    DataType _data;               // 结点中的数据域
};

2.二叉树

 2.1二叉树概念

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

 2.2 二叉树的特点:
      •每个结点最多有两棵子树,即二叉树不存在度大于2 的结点
      •二叉树的子树有左右之分,其子树的次序不能颠倒。
 2.3 特殊的二叉树:
 2.3.1 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉 树。也就是说,如果一个二叉树的层数为K ,且结点总数是 (2^k) -1 ,则它就是满二叉树。
 2.3.2 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K 的,有 n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号 从1 n 的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
2.4二叉树的存储结构
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
二叉树的性质:
(1). 若规定根节点的层数为 1 ,则一棵非空二叉树的 i 层上最多有 2^(i-1) 个结点 .
(2). 若规定根节点的层数为 1 ,则 深度为 h 的二叉树的最大结点数是 2^h- 1 .
(3). 对任何一棵二叉树 , 如果度为 0 其叶结点个数为 n0, 度为 2 的分支结点个数为 n2, 则有 n0 n2 1
(4). 若规定根节点的层数为 1 ,具有 n 个结点的满二叉树的深度 h=LogN
2.4.1顺序结构
        顺序结构存储就是使用 数组来存储 ,一般使用数组 只适合表示完全二叉树 ,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储后面说堆。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。 看下图:
2.4.2链式结构
        二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。链式结构又分为二叉链和三叉链,当前一般都是二叉链,对于高阶数据结构如红黑树等会用到三叉链。
// 二叉链
structBinaryTreeNode
{
    structBinTreeNode*pLeft;   // 指向当前节点左孩子   
    structBinTreeNode*pRight; // 指向当前节点右孩子    
    BTDataType_data; // 当前节点值域
}


// 三叉链
structBinaryTreeNode
{
    structBinTreeNode*pParent; // 指向当前节点的双亲    
    structBinTreeNode*pLeft;   // 指向当前节点左孩子    
    structBinTreeNode*pRight; // 指向当前节点右孩子
    BTDataType_data; // 当前节点值域
};
2.4.3二叉树链式结构遍历
•前序/ 中序 / 后序的递归结构遍历 :是根据访问结点操作发生位置命名
(1). NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。 简记:根->左子树->右子树
(2). LNR :中序遍历 (Inorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之中。
简记:左子树->根->右子树
(3). LRN:后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。 简记:左子树->右子树->根
(4).由于被访问的结点必是某子树的根,所以 N(Node )、 L(Left subtree )和 R(Right subtree) 可解释为根、根的左子树和根的右子树 。NLR、 LNR LRN 分别又称为先根遍历、中根遍历和后根遍历。
•层序遍历
除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1 ,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2 层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

3.二叉树代码实现 

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>


//二叉树节点 结构
typedef char BTDataType;

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


//     Queue  队列节点结构  以及调用队列接口函数
typedef struct BinaryTreeNode* QDataType;  //typedef int QDataType;
typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QNode;
typedef struct Queue
{
	QNode* head;
	QNode* tail;
}Queue;

void QueueInit(Queue* pq)//初始化队列
{
	assert(pq);
	pq->head = pq->tail = NULL;
}

void QueueDestory(Queue* pq)//销毁队列
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* temp = cur->next;
		free(cur);
		cur = temp;
	}
	pq->head = pq->tail = NULL;
}

void PrintQueue(Queue* pq)//打印队列数据
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}
QNode* Buynode(QDataType x)//申请节点
{
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
}

void QueuePush(Queue* pq, QDataType x)//队尾插入数据(队尾进)
{
	assert(pq);
	if (pq->head == NULL)
	{
		pq->head = pq->tail = Buynode(x);
	}
	else
	{
		pq->tail->next = Buynode(x);
		pq->tail = pq->tail->next;
	}
}
void QueuePop(Queue* pq)//队头删除数据(队头出)
{
	assert(pq);
	if (pq->head == pq->tail)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* temp = pq->head->next;
		free(pq->head);
		pq->head = temp;
	}
}
bool QueueEmpty(Queue* pq)//判断队列是否为空
{
	assert(pq);
	return pq->head == NULL;
}
size_t QueueSize(Queue* pq)//队列的长度
{
	assert(pq);
	size_t size = 0;
	QNode* cur = pq->head;
	while (cur)
	{
		size++;
		cur = cur->next;
	}
	return size;
}
QDataType QueueFront(Queue* pq)//输出队头的数据值
{
	assert(pq);
	assert(pq->head);
	return pq->head->data;
}

QDataType QueueBack(Queue* pq)//输出队尾的数据值
{
	assert(pq);
	assert(pq->tail);
	return pq->tail->data;
}
//     Queue结束



void PrevOrder(BTNode* root)//前序(前根)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}
void InOrder(BTNode* root)//中序(中根)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}

void PostOrder(BTNode* root)//后序(后根)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->data);
}

//void TreeSize(BTNode* root,int *size)//无返回值 计算节点个数
//{
//	if (root == NULL)
//	{
//		return;
//	}
//	else
//	{
//		++(*size);   //++优先级高于*,所以解引用要加上括号;
//	}
//	TreeSize(root->left, size);
//	TreeSize(root->right,size);
//
//}
//int TreeSize(BTNode* root, int* size)//有返回值 计算节点个数
//{
//	if (root == NULL)
//	{
//		return;
//	}
//	else
//	{
//		++(*size);   //++优先级高于*,所以解引用要加上括号;
//	}
//	TreeSize(root->left, size);
//	TreeSize(root->right, size);
//	return *size;
//}
int TreeSize(BTNode* root)//有返回值,利用"后序"思想,计算节点个数
{
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

//计算叶子节点个数
int TreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right== NULL)
	{
		return 1;
	}
	TreeLeafSize(root->left);
	TreeLeafSize(root->right);

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

void LevelOrder(BTNode* root)//层序遍历 
{
	Queue qt;//创建队列
	QueueInit(&qt);//队列初始化
	if (root == NULL)
	{
		return;
	}
	else
	{
		QueuePush(&qt, root);
	}
	while (!QueueEmpty(&qt))
	{
		BTNode* front = QueueFront(&qt);
		QueuePop(&qt);
		printf("%c ", front->data);
		if (front->left)
		{
			QueuePush(&qt, front->left);
		}
		if (front->right)
		{
			QueuePush(&qt, front->right);
		}
	}
	printf("\n");
	QueueDestory(&qt);//队列销毁
}

int main()
{
	BTNode* A = (BTNode*)malloc(sizeof(BTNode));
	A->left = NULL;
	A->right = NULL;
	A->data = ' A';

	BTNode* B = (BTNode*)malloc(sizeof(BTNode));
	B->left = NULL;
	B->right = NULL;
	B->data = ' B';

	BTNode* C = (BTNode*)malloc(sizeof(BTNode));
	C->left = NULL;
	C->right = NULL;
	C->data = ' C';

	BTNode* D = (BTNode*)malloc(sizeof(BTNode));
	D->left = NULL;
	D->right = NULL;
	D->data = 'D';

	BTNode* E = (BTNode*)malloc(sizeof(BTNode));
	E->left = NULL;
	E->right = NULL;
	E->data = 'E';

	A->left = B;
	A->right = C;
	B->left = D;
	B->right = E;

	PrevOrder(A);//A B D NULL NULL E NULL NULL C NULL NULL
	printf("\n");
	InOrder(A); //NULL D NULL B NULL E NULL A NULL C NULL
	printf("\n");
	PostOrder(A);// NULL NULL D NULL NULL E B NULL NULL C A
	printf("\n");

	//计算节点个数
	//int size_A = 0;
	//TreeSize(A, &size_A);
	//printf("TreeNode:%d\n", size_A);
	//int size_B = 0;
	//TreeSize(B, &size_B);
	//printf("TreeNode:%d\n", size_B);
	printf("TreeNode:%d\n", TreeSize(A));
	printf("TreeNode:%d\n", TreeSize(B));

	//计算叶子节点个数
	printf("TreeLeafSize:%d\n", TreeLeafSize(A));
	printf("TreeLeafSize:%d\n", TreeLeafSize(B));

	LevelOrder(A);//打印每一层的节点: A B C D E

	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值