普通二叉树的C语言实现

本文使用C语言实现了普通二叉树的创建、遍历、深度等一系列算法。

1.代码部分

首先进行二叉树结构体的定义:

1
2
3
4
typedef struct BiNode {
char data;
struct BiNode* lchild, * rchild;
}BiNode, *BiTree;

在二叉树的非递归算法中,使用到了栈与队列,其定义如下

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct {
BiTree base;
BiTree top;
int stacksize;
}SqStack;


typedef struct {
BiNode* data;
int front;
int rear;
}SqQueue;

二叉树递归的遍历方法,其实内含了函数栈在其中,所谓非递归的第一种算法就是人为创建了一个栈,将结点压栈与出栈。另一种非递归方法则是层次遍历,从上至下从左至右,利用队列依次读取。

首先进行二叉树的初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void CreatBiTree(BiTree* T)  // Function CreatBiTree create position for every node, so pass in the pointer.
{ // Also, lchild & rchild are both pointer, for the function can be valid with T and its children here needs a pointer points at BiTree rather than a single struct.
char ch = ' ';
printf("Print pre-order-traverse:\n");
scanf_s("%c", &ch, 8);
setvbuf(stdin, NULL, _IOFBF, 512);
if (ch == '#') { *T = NULL; } //'#' means the current node is an empty node.
else{
*T = (BiNode*)malloc(sizeof(BiNode));
if (!*T)
{
printf("ERROR!\n");
}
else
{
(*T)->data = ch;
CreatBiTree(&(*T)->lchild); // Pay attention: pass in the address here.
CreatBiTree(&(*T)->rchild);
}
}
}

创建函数用递归的方法,以先序遍历的顺序依次读入结点信息。若子树为空则用‘#’符号填补。注意:BiTree本身就是一个指针,传入的时候传入的是BiTree的地址。

创建完成后,可进行遍历。首先采取先序、中序、后序三种递归遍历方式。

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
void PreOrderTraverse(BiTree T)  // Traverse order: root, leftchild, rightchild
{
if (T == NULL)
{
printf("");
}
else
{
printf("%c ", T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}



void InOrderTraverse(BiTree T) // Traverse order: leftchild, root, rightchild
{
if (T == NULL)
{
printf("");
}
else
{
InOrderTraverse(T->lchild);
printf("%c ", T->data);
InOrderTraverse(T->rchild);
}
}


void AfOrderTraverse(BiTree T)
{
if (T == NULL)
{
printf("");
}
else
{
AfOrderTraverse(T->lchild);
AfOrderTraverse(T->rchild);
printf("%c ", T->data);
}
}

其中printf这一行可以依据所需进行更改,广义的表示为对这个结点进行访问。

递归方式求子叶、求深度函数比较简单,不做赘述。

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
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;}
}

}


int NodeCount(BiTree T)
{
if (T == NULL) { return 0; }
else { return (NodeCount(T->lchild) + NodeCount(T->rchild) + 1); }
}


int LeafCount(BiTree T)
{
if (T == NULL)
{
return 0;
}
else
{
if (T->lchild == NULL && T->rchild == NULL)
{
return 1;
}
else
{
return (LeafCount(T->lchild) + LeafCount(T->rchild));
}
}
}

非递归遍历之一:自己创建一个栈

基本思想:根节点进栈,遍历其左子树;根节点出栈,进行访问(输出);遍历右子树。

递归过程计算机自动实现的函数栈过程如下图:

函数栈的机制

自己创建栈过程代替上面的过程:

基本函数(初始化栈、压栈、出栈、判断栈空)

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
void Init_sq(SqStack* sta)  // Functio Init spares memory for pointer base and top.
{
sta->base = (BiTree)malloc(100 * sizeof(BiTree));
if (!sta->base) { printf("error!"); }
sta->top = sta->base = NULL;
sta->stacksize = 100;
}


void Push(BiTree T, SqStack* sta)
{
if (sta->top - sta->base == sta->stacksize) { printf("ERROR! Stack is full.\n"); }
else
{
sta->top->data = T->data; // Here 'sta->top = T' doesn't work because this will change the position of pointer top.
sta->top->rchild = T->rchild;
sta->top->lchild = T->lchild;
sta->top++;
}
}


BiTree Pop(BiTree T, SqStack* sta)
{
if (sta->base == sta->top) { printf("ERROR!"); return NULL; }
else
{
sta->top--;
T = sta->top;
return T;
}
}


int StackEmpty(SqStack* sta)
{
if (sta->base == sta->top) { return 0; }
else { return 1; }
}

遍历过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void StackTraverseInOrder(BiTree T, SqStack* sta)
{
BiTree p = (BiNode*)malloc(sizeof(BiNode));
BiTree q = (BiNode*)malloc(sizeof(BiNode));
p = T; q = T;
sta->top = sta->base = T;
while (p || StackEmpty(sta) == 1)
{
if (p)
{
Push(p, sta); // Q1: Why sta->base changes after this step? A1: When the value of a pointer changes, the pointer's position changes.
p = p->lchild;
}
else
{
q = Pop(q, sta);
printf("%c ", q->data);
p = q->rchild;
}
}
}

值得注意的是:1.简单初始化之后,若不对sta的内部成员(top、base)赋值,在代码运行过程中每加入一个新元素都会是top、base的地址发生大改变,而不是单纯的top++。由于SqStack结构体内部top、base也都是指针,直接改变指针的值得操作过于繁琐。2.两个指针直接相等会导致地址变得一直,若想让两个指针的内容一直应对内部成员逐个赋值。

类似的,层次遍历的基本问题与思路都差不多。只需注意一点,在malloc分配内存时,只有数组才有n*sizeof(xxx)有意义,代表了数组大小,其他的结构体指针的分配貌似没有什么扩大空间的必要。

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
SqQueue* Initial(SqQueue* que)
{
que->front = que->rear = 0;
que->data = (BiNode*)malloc(100 * sizeof(BiNode));
if (!que->data) { printf("ERROR!"); }
return que;
}


void EnQueue(SqQueue* que, BiNode* T)
{
// The method of using less than one spatial element is used to distinguish empty queues from full queues.
if ((que->rear + 1) % MAXSIZE == que->front) { printf("Queue is full. Fail to insert.\n"); }
else {
que->data[que->rear] = *T;
que->rear = (que->rear + 1) % MAXSIZE; // Make queue a circle.
}
}


BiTree DeQueue(SqQueue* que, BiTree T)
{
if (que->front == que->rear) { printf("Queue is empty.\n"); }
*T = que->data[que->front];
que->front = (que->front + 1) % MAXSIZE;
return T;
}


int QueueEmpty(SqQueue* que)
{
if (que->front == que->rear) { return 0; }
else { return 1; }
}


void LevelOrder(BiTree T, SqQueue* que)
{
BiTree new = (BiTree)malloc(sizeof(BiTree));
Initial(que);
EnQueue(que, T);
while (QueueEmpty(que) != 0)
{
new = DeQueue(que, new);
printf("%c ", new->data);
if (new->lchild != NULL)
{
EnQueue(que, new->lchild);
}
if (new->rchild != NULL)
{
EnQueue(que, new->rchild);
}
}
}

主函数部分:

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
int main() {
int d;
int num;
int leaf;
BiTree BT = (BiNode*)malloc(sizeof(BiNode));
SqStack* sqs = (SqStack*)malloc(sizeof(SqStack));
SqQueue* sqq = (SqQueue*)malloc(sizeof(SqQueue));
if (BT)
{
CreatBiTree(&BT);
}
printf("PreOrderTraverse: ");
PreOrderTraverse(BT);
printf("\n");
printf("InOrderTraverse: ");
InOrderTraverse(BT);
printf("\n");
printf("AfOrderTraverse: ");
AfOrderTraverse(BT);
printf("\n");
d = Depth(BT);
printf("The depth of binary tree is: %d\n", d);
num = NodeCount(BT);
printf("Total node: %d\n", num);
leaf = LeafCount(BT);
printf("Total leaves: %d\n", leaf);
printf("----------------------------\n");
Init_sq(sqs);
printf("InOrderTraverse with stack: ");
StackTraverseInOrder(BT, sqs);
printf("\nLevelOrder: ");
LevelOrder(BT, sqq);
return 0;
}

自行按需调试即可。

2.反思与改进

二叉树的操作代码出了很多问题,改了很久很久。对于到底传入一级指针还是二级指针,对于内存的分配,对于指针地址的变化情况都出现了令人头疼的问题。希望一直坚持下去。同时对于内存的释放还是理解的很不够,代码中并没有释放内存因为很多分配好的指针的地址被改变了。解决办法尚未找到。

查看评论