顺序树
这里是直接按层次遍历的顺序将树的元素写入
代码实现
#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;
}
提一提我花了一个小时排错,排出了各种各样奇奇怪怪的不对后发现是我符号打错了的心灰意冷