用来描述结点之间有分支,具有层次关系的结构
树(Tree)是n(n>=0)个结点的有限集。
若n=0,称为空树;
若n>0,则满足如下两个条件:
有且仅有一个特定的root
其余结点可分为m个互不相交的有限集合,其中每个集合本身也是一棵子树
二叉树是n(n>=0)各结点的有限集,或者是空集(n=0),或者由一个根结点及两棵互不相交的分别称作这个根的左子树和右子树的二叉树组成
空二叉树,根和空的左右子树,根和左子树,根和右子树,根和左右子树
实现:按满二叉树的结点层次编号,用数组依次存放二叉树中的数据元素
顺序存储结构适用于满二叉树和完全二叉树
#define MAXSIZE 100
typedef int SqBiTree[MAXSIZE];
SqBiTree bt;
二叉树的顺序存储缺点:
最坏情况:深度为k的且有k个结点的单支树需要长度为2^k-1的一维数组
二叉树结点的特点:parent,lchild,rchild,data
存储方式:[lchild,data,rchild] 其中结点缺少左右孩子,则对应的指针为空,
可推测在n个结点的二叉链表中,有n+1个空指针域
// 二叉链表
typedef struct BiNode{
int data;
struct BiNode * lchild; // 左孩子
struct BiNode * rchild; // 右孩子
}BiNode, *BiTree;
三叉链表:[lchild,data,parent,rchild]
// 三叉链表
typedef struct TriTNode {
int data;
struct TriTNode *lchild, *parent, *rchild;
}TriTNode, *TriTree;
遍历定义:顺着某一条搜索路径巡防二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次(又称周游)
遍历的目的:得到树中所有结点的一个线性排列
// 先序遍历
void PreOrderTraverse(BiTree t) {
if (t == NULL) {
printf("tree is NULL");
} else {
printf("%d\t", t->data);
PreOrderTraverse(t->lchild);
PreOrderTraverse(t->rchild);
}
}
// 中序遍历
void InOrderTraverse(BiTree t) {
if (t == NULL) {
printf("tree is NULL");
} else {
InOrderTraverse(t->lchild);
printf("%d\t", t->data);
InOrderTraverse(t->rchild);
}
}
// 后序遍历
void PostOrderTraverse(BiTree t) {
if (t == NULL) {
printf("tree is NULL");
} else {
PostOrderTraverse(t->lchild);
PostOrderTraverse(t->rchild);
printf("%d\t", t->data);
}
}
// 结论:从递归的角度看,三种算法是完全相同的,时间效率On,或者说这三种算法的访问路径是相同的,只是访问结点的时机不同。
// 从根节点出发到终点的路径上,每个结点经过三次,第一次经过时访问=先序遍历;第二次经过时访问=中序遍历;
// 第三次经过时访问=后序遍历
我们需要手撕一个栈的基本操作
// 中序遍历非递归算法:入栈根结点,遍历根结点的左子树为空时,出栈根结点继续访问右子树。
typedef BiNode* STDataType;
typedef struct Stack {
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST * ps) {
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
bool StackEmpty(ST* ps) {
assert(ps);
return ps->top == 0;
}
void StackPush(ST * ps, STDataType x) {
assert(ps);
if (ps->top == ps->capacity) {
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity *2;
STDataType * tmp = realloc(ps->a, sizeof(STDataType)*newCapacity);
if (tmp == NULL) {
printf("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps) {
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
STDataType StackTop(ST* ps) {
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
// 中序非递归遍历二叉树算法
void InorderTraverse(BiTree t, ST *S) {
BiTree p = t;
StackInit(S);
while (p || StackEmpty(S)) {
if (p) {
StackPush(S,p);
p = p->lchild;
} else {
p = StackTop(S);
StackPop(S);
printf("%c", p->data);
p = p->rchild;
}
}
}
// 二叉树的层次遍历:对于一棵二叉树,从根结点开始,按从上到下,从左到右的顺序访问每一个结点,每个结点仅访问一次
// 将根结点进队,队不空时循环:从队列中出队一个结点p,访问它;若p有左孩子,将左孩子进队;若p有右孩子,将右孩子进队
typedef BiNode* QDataType;
typedef struct QueueNode {
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue {
QNode * head;
QNode * tail;
}Queue;
void QueueInit(Queue* pq) {
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
void QueuePush(Queue* pq, QDataType x) {
assert(pq);
QNode * newNode = (QNode*)malloc(sizeof(QNode));
newNode->data = x;
if (pq->head == NULL) {
pq->head = pq->tail = newNode;
} else {
pq->tail->next = newNode;
pq->tail = newNode;
}
}
bool QueueEmpty(Queue* pq) {
assert(pq);
return pq->head == NULL;
}
void QueuePop(Queue* pq) {
assert(pq);
assert(!QueueEmpty(pq));
pq->head = pq->head->next;
free(pq->head);
if (pq->head == NULL) {
pq->tail = NULL;
}
}
void LevelOrder(BiTree t) {
BiTree p = t;
Queue *q = NULL;
QueueInit(q);
QueuePush(q, p->data);
while (!QueueEmpty(q)) {
QueuePop(q);
printf("%c", p->data);
if (p->lchild != NULL) {
QueuePush(q, p->lchild);
}
if (p->rchild != NULL) {
QueuePush(q, p->rchild);
}
}
}
void CreateBiTree(BiTree t) {
char ch;
scanf("%s", &ch);
if (ch == "#") {
t = NULL;
} else {
t = (BiNode*)malloc(sizeof(BiNode));
assert(t);
t->data = ch;
CreateBiTree(t->lchild);
CreateBiTree(t->rchild);
}
}
// 计算二叉树的深度
// 如果是空树,则深度为0
// 否则递归计算左子树的深度记为m,递归计算右子树的深度记为n,二叉树的深度则为m与n的较大者加一
int Depth(BiTree t) {
int m, n;
if (t == NULL) {
return 0;
} else {
m = Depth(t->lchild);
n = Depth(t->rchild);
if (m > n) {
return m + 1;
} else {
return n + 1;
}
}
}
/*
* 计算二叉树的结点个数;
* 如果是空树,则返回0
* 否则,结点个数为左子树的结点数+右子树的结点个数+1
*/
int NodeCount(BiTree t) {
if (t == NULL) {
return 0;
} else {
return NodeCount(t->lchild) + NodeCount(t->rchild) + 1;
}
}
满二叉树在同样深度的二叉树中结点个数组最多
满二叉树在同样深度的二叉树中叶子结点个数最多
在满二叉树中,从最后一个结点开始,连续去掉任意个结点,即是一棵完全二叉树,一定是连续去掉!!!
叶子只可能分布在层次最大的两层上
对任一结点,如果其右子树的最大层次为i,其左子树的最大层次必为i或i+1
当用二叉链表作为二叉树的存储结构时,可以很方便的找到某个结点的左右孩子;但一般情况下,无法直接找到该结点在某种遍历序列中的前驱和后继结点。
利用二叉链表的中的空指针域:如果某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱;如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继
为区分lchild和rchild指针到底指向的是孩子,还是指向前驱或者后继,对二叉链表中每个结点增设两个标志位ltag和rtag,并约定:ltag=0,lchild指向该结点的左孩子;ltag=1,lchild指向该结点的前驱
typedef struct BiThrNode {
int data;
int ltag,rtag;
struct BiThrNode *lchild, *rchild;
}BiThrNode, *BiThrTree;
ltag=0,lchild指向根结点
rtag=1,rchild指向遍历序列中最后一个结点
遍历序列中第一个结点的lchild域和最后一个结点的rc域都指向头结点