二叉树的C语言实现

本文使用C语言实现了普通二叉树的创建、遍历、深度等一系列算法。欢迎关注个人博客:https://guotianyu-2020.github.io。

1.代码部分

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

typedef struct BiNode {
	char data;
	struct BiNode* lchild, * rchild;
}BiNode, *BiTree;

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

typedef struct {
	BiTree base;
	BiTree top;
	int stacksize;
}SqStack;


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

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

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

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的地址。

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

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这一行可以依据所需进行更改,广义的表示为对这个结点进行访问。

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

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

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

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

递归过程计算机自动实现的函数栈过程如下图:
函数栈的实现过程
自己创建栈过程代替上面的过程:

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

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

遍历过程:

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)有意义,代表了数组大小,其他的结构体指针的分配貌似没有什么扩大空间的必要。

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

主函数部分:

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.反思与改进

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值