C语言实现树(顺序树,链树)的创建,各种遍历,线索化等操作

 顺序树

这里是直接按层次遍历的顺序将树的元素写入

代码实现

#include <stdio.h>
#include <stdlib.h>
#define maxsize 100
//创建树的结构体
typedef struct Node{
	int data[maxsize];
	int size;
}Node,*Tree;
//创建树
Tree createTree(){
	Tree tr =(Tree)malloc(sizeof(Node));
	if(tr == NULL)
	{
		printf("创建失败\n");
		return NULL;
	}
	tr->size = 0;

	return tr;
}

//插入元素
void insertNode(Tree tr,int value)
{
	if(tr->size >= maxsize)
	{
		printf("树已经满了\n");
		return;
//当return语句后面没有跟任何表达式时,它的作用是直接结束函数的执行并返回到调用该函数的地方。这样做可以避免继续执行函数中的其他代码。
	}
	tr->data[++tr->size] = value;
}

//层序遍历
void levelOrderTraversal(Tree tr)
{
	if(tr->size == 0){
		return;
	}
	for(int i = 1;i <= tr->size;i++){
		printf("%d ",tr->data[i]);
	}
}
//前序遍历
void preOrderTraversal(Tree tr,int index)
{
	if(index > tr->size){
		return;
	}
	printf("%d ",tr->data[index]);
	preOrderTraversal(tr,2 * index);
	preOrderTraversal(tr,2 * index + 1);
}
//中序遍历
void inOrderTraversal(Tree tr,int index)
{
	if(index > tr->size){
		return;
	}
	inOrderTraversal(tr,2 * index);
	printf("%d ",tr->data[index]);
	inOrderTraversal(tr,2 * index + 1);
}

//后序遍历
void postOrderTraversal(Tree tr,int index)
{
	if(index > tr->size){
		return;
	}
	postOrderTraversal(tr,2 * index);
	postOrderTraversal(tr,2 * index + 1);
	printf("%d ",tr->data[index]);

}

int main()
{
	Tree tr = createTree();
	insertNode(tr,1);
	insertNode(tr,2);
	insertNode(tr,5);
	insertNode(tr,3);
	insertNode(tr,4);
	insertNode(tr,6);
	insertNode(tr,7);
   	printf("层次遍历二叉树:\n");
    	levelOrderTraversal(tr);
    	printf("\n");

   	 printf("先序遍历二叉树:\n");
   	 preOrderTraversal(tr, 1);
    	 printf("\n");

    	printf("中序遍历二叉树:\n");
    	inOrderTraversal(tr, 1);
    	printf("\n");

    	printf("后序遍历二叉树:\n");
    	postOrderTraversal(tr, 1);
    	printf("\n");

    	free(tr);

    	return 0;
}



链树

代码实现

/*
 ============================================================================
 Name        : Test1.c
 Author      : yolo
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ============================================================================
 */
#include <stdio.h>
#include <stdlib.h>
//定义树的结构体
typedef struct Node{
	int data;
	struct Node* left;
	struct Node* right;
}LNode,*Tree;
//初始化
//使用了传值方式来传递根节点指针,这会导致函数内部对根节点的修改不会影响到函数外部的根节点。
//因此,使用传引用方式来传递根节点指针
void initTree(Tree *root,int n){
	*root = (LNode*)malloc(sizeof(LNode));
	if(*root == NULL){
		printf("内存分配失败\n");
		return;
	}
	(*root)->data = n;
	(*root)->left = NULL;
	(*root)->right = NULL;
}
//插入结点
void insertNode(Tree root,int n){
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	LNode* p = root;
	LNode* q = NULL;
	if(newnode == NULL){
		printf("内存分配失败\n");
		return;
	}
	newnode->data = n;
	newnode->left = NULL;
	newnode->right = NULL;
	while(p){
		q = p;
		if(newnode->data < p->data){
			p = p->left;
		}else if(newnode->data > p->data){
			p = p->right;
		}else{
			printf("该数值已经存在\n");
			free(newnode);
			return;
		}
	}
	p = q;
	if(newnode->data < p->data){
		p->left = newnode;
	}else{
		p->right = newnode;
	}
}

//定义结点
typedef struct LinkNode{
	Tree tree;
	struct LinkNode* next;
}Node;
//定义队列
typedef struct QueueChain{
	Node* head;
	Node* rear;
}Queue,*QueueChain;
//入队
void inQueue(QueueChain s,Tree p){
	Node* q = (Node*)malloc(sizeof(Node));
	q->tree = p;
	q->next = NULL;
	if(s->head == NULL){
		s->head = q;
		s->rear = q;
	}else{
		s->rear->next = q;
		s->rear = q;
	}
}
//出队
//使用指向指针的指针是为了在函数内部修改指针的值。
//如果直接传递指针,则函数内部只能修改指针所指向的值,而不能修改指针本身。
//因此,在需要修改指针本身的情况下,通常使用指向指针的指针来传递指针。
void outQueue(QueueChain s,Tree *p)
{
	if(s->head == NULL){
		*p = NULL;
		return;
	}
	Node* q = s->head;
	*p = q->tree;
	s->head = q->next;
	free(q);
	if(s->head == NULL){
		s->rear = NULL;
	}
}
//层次遍历
void levelOrderTraverse(Tree tr){
	QueueChain s =(QueueChain)malloc(sizeof(Queue));
	s->head = NULL;
	s->rear = NULL;
	inQueue(s,tr);
	Tree p;
	while(s->head){
		outQueue(s,&p);
		printf("%d ",p->data);
		if(p->left){
			inQueue(s,p->left);
		}
		if(p->right){
			inQueue(s,p->right);
		}
	}
	free(s);
}

//前序遍历
void preOrderTraverse(Tree tr){
	if(tr){
		printf("%d ",tr->data);
		preOrderTraverse(tr->left);
		preOrderTraverse(tr->right);
	}
}
//中序遍历
void inOrderTraverse(Tree tr){
	if(tr){
		inOrderTraverse(tr->left);
		printf("%d ",tr->data);
		inOrderTraverse(tr->right);
	}
}

//后序遍历
void postOrderTraverse(Tree tr){
	if(tr){
		postOrderTraverse(tr->left);
		postOrderTraverse(tr->right);
		printf("%d ",tr->data);
	}
}
int main(){
    Tree head = (Tree)malloc(sizeof(LNode));
    initTree(&head, 4);
    insertNode(head, 2);
    insertNode(head, 1);
    insertNode(head, 3);
    insertNode(head, 6);
    insertNode(head, 5);
    insertNode(head, 7);

    printf("层次遍历二叉树:\n");
    levelOrderTraverse(head);
    printf("\n");
    printf("先序遍历二叉树:\n");
    preOrderTraverse(head);
    printf("\n");
    printf("中序遍历二叉树:\n");
    inOrderTraverse(head);
    printf("\n");
    printf("后序遍历二叉树:\n");
    postOrderTraverse(head);
    printf("\n");

    return 0;
}

注意:

为什么inQueue 函数和initTree 函数要传入的是Tree的指针

在 C 语言中,函数参数传递的方式是按值传递(pass by value),也就是说,当调用一个函数时,实际上传递给函数的是参数的值,而不是参数本身。因此,在函数内部对参数进行修改时,实际上是在修改函数内部的局部变量,而不是原始变量。

但是,如果参数是指针类型,那么传递的就是指针的地址,也就是指针变量本身的值。因此,在函数内部修改指针所指向的数据时,实际上是在修改原始变量的值。但如果在函数内修改指针本身(例如将指针指向另一个地址),则不会影响到原始变量。

inQueue 函数中,传入的是指向 QueueChain 结构体的指针 s 和指向 Tree 结构体的指针 p。因为这两个参数都是指针类型,所以传递的是指针变量本身的值。当在函数内部修改 p 指针所指向的数据时,实际上是在修改原始变量的值,因此可以通过传值方式来修改指针所指向的数据。

而在 initTree 函数中,传入的是指向 Tree 结构体的指针 root。因为这个参数也是指针类型,所以传递的也是指针变量本身的值。但是,在函数内部修改 root 指针时,实际上是在修改函数内部的局部变量 root 的值,而不是原始变量的值。这就导致了函数外部的根节点指针没有被修改。因此,在这种情况下,需要使用指向 Tree 结构体指针的指针,并将其作为函数参数传递进去,这样才能修改原始变量的值。

中序线索化树

代码实现

#include <stdio.h>
#include <stdlib.h>
typedef char DataType;
//定义线索二叉树结构体
typedef struct ThreadTree{
	DataType data;//存储数据的值
	struct ThreadTree* lchrild;//左孩子
	struct ThreadTree* rchild;//右孩子
	int ltag;//左标记
	int rtag;//右标记
}Node,*Tree;

//创建二叉树并初始化(左右标记置为0)
//0:有孩子,且指向孩子节点
//1:没有孩子,指向前驱

Tree createTree(){
	DataType x;
	Tree tr;
	scanf("%c",&x);
	if(x == '#')
		tr = NULL;
	else{
		tr =(Node*)malloc(sizeof(Node));
		tr->data = x;
		tr->ltag = 0;
		tr->rtag = 0;
		//先创建根节点再创建左右子树,使用先序遍历顺序创建二叉树
		tr->lchrild = createTree();
		tr->rchild = createTree();
	}
	return tr;
}
//方法一定义全局变量pre
Tree pre = NULL;

//方法二定义静态变量
//static Tree pre = NULL;


//线索化
void visit(Tree tr){
	if(tr->lchrild == NULL){//左子树为空,建立前驱线索
		tr->ltag = 1;
		tr->lchrild = pre;
	}
	if(pre && pre->rchild == NULL)
	{
		pre->rchild = tr;//将前驱结点的右孩子指向后继
		pre->rtag = 1;
	}
	pre = tr;
}
//中序遍历二叉树,一边遍历一边线索化
void inThread(Tree tr){
	if(tr){
		inThread(tr->lchrild);
		visit(tr);
		inThread(tr->rchild);
	}
}
//寻找后继结点
Tree next(Tree tr){
	if(tr->rtag == 1)
		tr = tr->rchild;
	else{
//右标志为0,不能直接找到后继结点,需要找到右子树最左下角的节点
//这是因为在中序遍历中,当前节点的右子树的最左下角的节点是当前节点的后继节点。
		tr = tr->rchild;
		while(tr->ltag == 0)
		{
			tr = tr->lchrild;
		}
	}
	return tr;
}
//寻找前驱结点
Tree prior(Tree tr){
	if(tr->ltag)
		tr = tr->lchrild;
	else{
		tr = tr->lchrild;
		while(tr->ltag == 0){
			tr = tr->rchild;
		}
	}
	return tr;
}
//中序遍历打印
//在中序线索化二叉树中,节点的 ltag 字段用于标记左子树指针 lchild 的类型,
//如果 ltag 为0,表示 lchild 指向节点的左子树;
//如果 ltag 为1,表示 lchild 指向节点的前驱节点。
//根据中序遍历的特点,需要找到中序遍历序列中的第一个节点。
//在中序线索化二叉树中,第一个节点是最左下角的节点,即没有左子树的节点。
//所以,可以通过循环不断将 tr 的左孩子指针 lchild 赋值给 tr
//直到找到一个节点,其左标记 ltag 为1,即没有左子树。
void inorderTraverse(Tree tr)
{
	if(tr == NULL)
		return;
	while(tr->ltag == 0)//查找第一个结点
	{
//因为二叉树的创建creat是以先序遍历序列创建
//所以t所指向的第一个结点并不是中序遍历所要访问的第一个结点
			tr = tr->lchrild;
	}
	printf("%c ",tr->data);
	while(tr->rchild)
	{
	// 此处以"t的右孩子不为空"为循环条件,是因为,
		//先前设定了最后一个结点的"后继"为空,表示结束
		//根据线索访问后续结点
		tr = next(tr);
		printf("%c ",tr->data);
	}
}

int main()
{
	Tree root;
	printf("请输入:(先序遍历,#代表无)");
	root = createTree();
	printf("\n");

	printf("线索化二叉树构建完毕\n");
	inThread(root);
	printf("\n");

	printf("中序遍历序列打印:");
	inorderTraverse(root);
	printf("\n");
	return 0;
}

先序线索化树

代码实现

#include<stdio.h>
#include<stdlib.h>
typedef struct Tree
{
	char ch;
	struct Tree* leftchild, * rightchild;
	int left, right;
}Tree;

Tree* pre;

/*创建一颗普通的二叉树*/
void create(Tree** T)
{
	char ch;
	scanf("%c", &ch);
	if (ch == '#')
	{
		(*T) = NULL;
	}
	else
	{
		(*T) = (Tree*)malloc(sizeof(Tree));
		(*T)->ch = ch;
		create(&(*T)->leftchild);
		create(&(*T)->rightchild);
	}
}

void xianxu(Tree* T)
{
	if (T)
	{
		printf("%c", T->ch);
		xianxu(T->leftchild);
		xianxu(T->rightchild);
	}
}

void qianxu(Tree* root)    /*线索化根为root的二叉树*/
{
	if(root)
	{
		if (root->leftchild == NULL)
		{
			root->left = 1;
			root->leftchild = pre;
		}
		else
		{
			root->left = 0;
		}

		if (root->rightchild!=NULL)
		{
			root->right = 0;
		}

		if (pre != NULL)
		{
			if (pre->rightchild == NULL)
			{
				pre->right = 1;
				pre->rightchild = root;
			}
			else
			{
				pre->right = 0;
			}
		}
		pre = root;


		if(root->left==0)
		qianxu(root->leftchild);

		if(root->right==0)
		qianxu(root->rightchild);

	}
}
void createhead(Tree** head, Tree* T)
{
	(*head) = (Tree*)malloc(sizeof(Tree));
	(*head)->left = 0;
	(*head)->right = 0;
	(*head)->rightchild = (*head);

	if (T == NULL)
	{
		(*head)->leftchild = (*head);
	}
	else
	{
		(*head)->leftchild = T;
		pre = (*head);
		qianxu(T);
		pre->rightchild = (*head);
		pre->right = 0;
		(*head)->rightchild = pre;
	}
}
/*非递归遍历中序线索二叉树*/
void bianli(Tree* head)
{
	Tree* p = head->leftchild;
	while (p != head)
	{
		while (p->left == 0)      /*从左节点开始,边遍历边输出*/
		{
			printf("%c", p->ch);
			p = p->leftchild;
		}
		printf("%c", p->ch);
		p = p->rightchild;     /*指向直接后继节点*/
	}
}



int main()
{
	//测试案例:ABD#G###CE##F##
	Tree* t;
	Tree* head;
	printf("请输入节点数据('#'为空):\n");
	create(&t);
	printf("先序遍历如下:\n");
	xianxu(t);
	printf("\n前序线索化非递归遍历如下:\n");
	createhead(&head, t);
	bianli(head);
	return 0;
}

注意:

这里的线索化时需要判断ltag和rtag是否为0,为了避免陷入死循环,因为当线索化左孩子后,若不判断,tr指针又会回到根节点,pre则指向它的左孩子,然后tr又指向左孩子,陷入死循环

非递归实现树的遍历

代码实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//测试abd##e##c#f##
typedef char DataType;
//定义线索二叉树结构体
typedef struct ThreadTree{
	DataType data;//存储数据的值
	struct ThreadTree* lchrild;//左孩子
	struct ThreadTree* rchild;//右孩子
}Node,*Tree;
Tree createTree(){
	DataType x;
	Tree tr;
	scanf("%c",&x);
	if(x == '#')
		tr = NULL;
	else{
		tr =(Node*)malloc(sizeof(Node));
		tr->data = x;
		//先创建根节点再创建左右子树,使用先序遍历顺序创建二叉树
		tr->lchrild = createTree();
		tr->rchild = createTree();
	}
	return tr;
}
//定义栈的结构体
typedef struct Node{
	Tree data;
	struct Node* next;
}SNode,*Stack;
//初始化

Stack initStack()
{
    SNode* head = (SNode*)malloc(sizeof(struct Node));
    if(head == NULL)
    {
        printf("内存不足\n");
        return NULL;
    }else{
        head->data = NULL;
        head->next = NULL;
        return head;
    }
}

//入栈
void push(Stack s,Tree tr){
	SNode* n = (SNode*)malloc(sizeof(struct Node));
	n->data = tr;
	n->next = s->next;
	s->next = n;
}
//判断栈空
bool empty(Stack s)
{
    if(s->next == NULL)
        return true;
    else
        return false;
}

//出栈
Tree pop(Stack s){
	if (!empty(s)) {
	SNode* n = (SNode*)malloc(sizeof(struct Node));
	n = s->next;
	s->next = n->next;
	Tree tr = n ->data;
	free(n);
	return tr;
	}else{
		return NULL;
	}
}

//打印函数
void visit(Tree tr){
	printf("%c ",tr->data);
}
//获取栈顶元素
Tree getTop(Stack s){
	return s->next->data;
}

//先序遍历
/*
 * 与中序遍历同样,只是在入栈前先访问根节点
 */
void preOrder(Tree tr){
	Stack s = initStack();
	Tree h = tr;
	while(h || !empty(s)){
		if(h){
			visit(h);
			push(s,h);
			h = h->lchrild;
		}
		else{
			h = pop(s);
			h = h->rchild;
		}
	}
}


//中序遍历
/*
 * 使用栈,先沿着根的左孩子一直遍历直到最左下角的叶子结点
 * 压入栈后栈顶元素一定是没有左孩子的结点且此时指针为空
 * 栈顶元素出栈同时将指针指向这个没有左孩子的结点的右孩子
 * 判断,若有右孩子则往下继续寻找左孩子,若没有则指针为空
 * 指针为空,输出的是这个既没有左孩子也没有右孩子的结点的双亲结点
 * 再判断这个双亲结点的右孩子
 */

void inOrder(Tree tr){
	Stack s = initStack();
	Tree h = tr;
	while(h || !empty(s)){
		if(h){
			push(s,h);
			h = h->lchrild;
		}
		else{
			h = pop(s);
			visit(h);
			h = h->rchild;
		}
	}
}

//后序遍历
/*
 * 先沿着根的左孩子依次入栈,找到最左边没有左孩子的结点
 * 判断是否有右孩子,没有就直接出栈,有就判断之前是否访问过
 * 若之前最近访问的结点是右孩子,说明右孩子这棵树已经遍历结束了,直接将栈顶元素出栈
 * 若最近访问的结点不是右孩子,说明右边的子树还没有遍历,将右孩子入栈
 * 每次出栈访问完一个结点就相当于遍历完以该节点为根的子树,需要将遍历指针置空
 */
void postOrder(Tree tr){
	Stack s = initStack();
	Tree h = tr;
	Tree r = NULL;
	while(h || !empty(s)){
		if(h){
			push(s,h);
			h = h->lchrild;
		}else{
			h = getTop(s);
			if(h->rchild && h->rchild != r){
				h = h->rchild;
			}else{
				h = pop(s);
				visit(h);
				r = h;
				h = NULL;
			}
		}
	}
}



int main()
{
	Tree root;
	printf("请输入:(先序遍历,#代表无)");
	root = createTree();
	printf("\n");

	printf("先序遍历序列打印:");
	preOrder(root);
	printf("\n");

	printf("中序遍历序列打印:");
	inOrder(root);
	printf("\n");

	printf("后序遍历序列打印:");
	postOrder(root);
	printf("\n");
	return 0;
}

提一提我花了一个小时排错,排出了各种各样奇奇怪怪的不对后发现是我符号打错了的心灰意冷

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摩尔曼斯克的海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值