深圳幻海软件技术有限公司 欢迎您!

【数据结构与算法】树与二叉树

2023-04-29

目录一.树1.树的定义2.结点的分类与关系3.树的相关概念4.树的表示方法二.二叉树1.二叉树的定义2.特殊二叉树3.二叉树的性质4.二叉树的顺序结构5.二叉树的链式结构(1)链式结构的创建(2)结点的创建(3)二叉树的手动构建(4)前中后序遍历(5)二叉树结点个数(6)二叉树的高度(7)第k层的结

目录

  • 一.树
    • 1.树的定义
    • 2.结点的分类与关系
    • 3.树的相关概念
    • 4.树的表示方法
    • 二.二叉树
      • 1.二叉树的定义
      • 2.特殊二叉树
      • 3.二叉树的性质
      • 4.二叉树的顺序结构
      • 5.二叉树的链式结构
        • (1)链式结构的创建
        • (2)结点的创建
        • (3)二叉树的手动构建
        • (4)前中后序遍历
        • (5)二叉树结点个数
        • (6)二叉树的高度
        • (7)第k层的结点个数
        • (8)查找二叉树中为x的结点
        • (9)层序遍历
        • (10)判断是否为完全二叉树
        • (11)销毁二叉树

一.树

1.树的定义

除了之前我们讲的栈、队列、链表等线性结构,数据结构中还有着一对多的非线性结构———
树是有n个结点组成的有限集,当n=0时为空树,在任意一颗非空树中,有且仅有一个特定的根结点 ;当n>1时,其余结点又可以分为一棵树,称为根的子树
如下图所示:

A为整棵树的根节点,分别以B、C为根节点能组成两颗子树:

2.结点的分类与关系

结点拥有的子树个数称为结点的度,一颗树结点为各个结点度的最大值。度为0的结点称为叶结点或者终端结点
一棵树中结点之间的关系,结点的子树的根结点称为该结点的孩子,所以该结点称为孩子结点的双亲结点。同一个双亲的孩子结点之间称为兄弟结点

3.树的相关概念

结点的层次就是从何根开始定义为第一层,根的孩子为第二层以此类推。树中结点的最大层次称为树的深度或者高度
森林是指由多颗互不相交的树的集合。

4.树的表示方法

树常使用双亲表示法:

typedef int DataType;
struct Node
{
 struct Node* firstChild1;    // 第一个孩子结点
 struct Node* pNextBrother;   // 指向其下一个兄弟结点
 DataType data;               // 结点中的数据域
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

二.二叉树

1.二叉树的定义

二叉树是度为2的树,二叉树的子树有左右之分所以二叉树是有序树

2.特殊二叉树

  1. 斜树顾名思义,斜树一定是斜的:所有结点都只有左子树的二叉树叫做左斜树,所有结点都只有右子树的二叉树叫做右斜树。这两者统称为斜树。
  2. 满二叉树:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点又在同一层上,这样的二叉树叫做满二叉树
  3. 完全二叉树,假设完全二叉树的有K层则此二叉树在前K-1层为满二叉树并且在第K层至少有一个结点,在第K层从左到右连续。

3.二叉树的性质

  1. 在二叉树的第k层至多有2^(k-1)个结点。
  2. 深度为k的二叉树至多有2^k-1个结点。
  3. 对于任何一棵二叉树,叶子结点数为n0,度为2的结点数为n2,则有n0=n2+1
  4. 具有n个结点的完全二叉树的深度为log以2为底n的对数加1
  5. 具有n个结点的二叉树具有n-1个边。

4.二叉树的顺序结构

二叉树的顺序结构就是使用一维数组存储二叉树的结点,但是只适合储存完全二叉树,也就是前面讲过的堆,所以这里不过多赘述,想了解的点击链接[堆和堆排序]。(http://t.csdn.cn/Sgjar)

5.二叉树的链式结构

现在我们来看下二叉树真正适合的存储结构-----链式结构。

(1)链式结构的创建

一个结点可以存储一个数据元素,以及左孩子、右孩子。

typedef int BTDataType;

typedef struct BinaryTreeNode
{
BTDataType data;//存储一个数据元素
struct BinaryTreeNode* left;//指向左孩子
struct BinaryTreeNode* right;//指向右孩子
}BTNode;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

(2)结点的创建

我们编写一个用来创建结点存储数据的函数,并将左右孩子初始化为空。

BTNode* BuyNode(BTDataType x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}

newnode->data = x;
newnode->left = NULL;
newnode->right = NULL;

return newnode;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

(3)二叉树的手动构建

我们使用创建结点函数手动构建出一个二叉树便于验证后续函数代码的正确:

BTNode* CreatTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
BTNode* node7 = BuyNode(7);


node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
node2->right = node7;

return node1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

(4)前中后序遍历

前中后序遍历的区别即是根结点如何访问的问题,eg:前序遍历:先访问根结点再访问左孩子最后右孩子。

void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}

printf("%d ", root->data);
PreOrder(root->left);
PreOrder(root->right);

}

void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}

InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}

void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}

PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

(5)二叉树结点个数

二叉树链式存储结构函数的编写多使用递归的方式,这里我们需要求得结点的个数只需要将左树的结点加上右树的再加1即可

int TreeSize(BTNode* root)
{
return root == NULL ? 0 :
TreeSize(root->left)
+ TreeSize(root->right)
+ 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

(6)二叉树的高度

二叉树的高度为树中结点的最大层次,比较左子树和右子树高度的较大值再加一个即可,需要注意的是需要记录左右子树的高度,不然递归的效率将会大大降低。

int TreeHeight(BTNode* root)
{
if (root == NULL)
{
return 0;
}

int leftHeight = TreeHeight(root->left);
int rightHeight = TreeHeight(root->right);

return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

(7)第k层的结点个数

利用分治递归的思想,想要求第k层的结点个数,只要分别求左右树第k-1层的结点个数并相加即可。


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

if (k == 1)
return 1;

int leftKLevel = TreeKLevel(root->left, k - 1);
int rightKLevel = TreeKLevel(root->right, k - 1);

return leftKLevel + rightKLevel;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

(8)查找二叉树中为x的结点

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;

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

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

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

return NULL;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

(9)层序遍历

层序遍历即是将每一层都访问后,继续访问下一层,显然之前学习的队列非常适合实现这一操作:其中重要的是将队列原先存储的元素改为二叉树的结点指针类型


typedef struct BinaryTreeNode* QDataType;///****

typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;

typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;


void QueueInit(Queue* pt);
void QueueDestroy(Queue* pt);

void QueuePush(Queue* pt, QDataType x);
void QueuePop(Queue* pt);
bool QueueEmpty(Queue* pt);
QDataType QueueFront(Queue* pt);

void QueueInit(Queue* pt)
{
assert(pt);

pt->head = pt->tail = NULL;
pt->size = 0;
}

void QueueDestroy(Queue* pt)
{
assert(pt);
QNode* cur = pt->head;

while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pt->head = pt->tail = NULL;
pt->size = 0;
}


void QueuePush(Queue* pt, QDataType x)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}

newnode->next = NULL;
newnode->data = x;

if (pt->head == NULL)
{
assert(pt->tail == NULL);
pt->head = pt->tail = newnode;
}
else
{
pt->tail->next = newnode;
pt->tail = newnode;
}

pt->size++;

}


void QueuePop(Queue* pt)
{
assert(pt);
assert(pt->head != NULL);

if (pt->head->next == NULL)
{
free(pt->head);
pt->head = pt->tail = NULL;
}
else
{
QNode* next = pt->head->next;

free(pt->head);
pt->head = next;
}
pt->size--;
}
bool QueueEmpty(Queue* pt)
{
assert(pt);

return pt->size == 0;
}

QDataType QueueFront(Queue* pt)
{
assert(pt);
assert(!QueueEmpty(pt));

return pt->head->data;
}

void LevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);

if (root)
QueuePush(&q, root);

while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);

printf("%d ", front->data);

if (front->left)
QueuePush(&q, front->left);

if (front->right)
QueuePush(&q, front->right);

}



QueueDestroy(&q);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138

(10)判断是否为完全二叉树

我们想要判断二叉树是否为完全二叉树,就要了解完全二叉树的特点:完全二叉树在前k-1层是满二叉树,在第k层的非空结点从左到右也是连续的。思路已有,代码实现:

bool BinaryTreeComplete(BTNode* root)
{
Queue eth;
QueueInit(&eth);

if (root)
QueuePush(&eth, root);

while (!QueueEmpty(&eth))
{
BTNode* front = QueueFront(&eth);
QueuePop(&eth);

if (front == NULL)
break;
else
{
QueuePush(&eth, front->left);
QueuePush(&eth, front->right);
}
}

while (!QueueEmpty(&eth))
{
BTNode* front = QueueFront(&eth);
QueuePop(&eth);

if (front)
{
QueueDestroy(&eth);
return false;
}
}

return true;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

(11)销毁二叉树

销毁二叉树我们依旧使用递归实现,需要注意的是在调用销毁函数后,需要在函数体外手动置空。

void TreeDestroy(BTNode* root)//外部手动置空
{
if (root == NULL)
return;

TreeDestroy(root->left);
TreeDestroy(root->right);

free(root);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览45269 人正在系统学习中