二叉树的一些基本操作 [附代码]

将二叉树bt中每一个结点的左右子树互换的C语言算法

typedef struct node {
    int data;
    struct node *lchild, *rchild;
} btnode;

void ADDQ(queue *Q, btnode *bt); // Assuming these are predefined
btnode* DELQ(queue *Q);           // Assuming these are predefined
int EMPTY(queue *Q);              // Assuming these are predefined

void EXCHANGE(btnode *bt) {
    queue Q;
    initQueue(&Q); // Assuming initQueue initializes the queue

    btnode *p, *q;
    if (bt) {
        ADDQ(&Q, bt); // Add root to the queue
        while (!EMPTY(&Q)) {
            p = DELQ(&Q); // Dequeue a node

            if (p->lchild) {
                ADDQ(&Q, p->lchild); // Enqueue left child
            }

            if (p->rchild) {
                ADDQ(&Q, p->rchild); // Enqueue right child
            }

            // Swap the left and right children
            q = p->lchild;
            p->lchild = p->rchild;
            p->rchild = q;
        }
    }
}

// Note: Ensure to implement or include necessary queue operations (ADDQ, DELQ, EMPTY, initQueue) for the complete functionality.

二叉树中序遍历的非递归算法

Status Inorder(BiTree T) {
    InitStack(s); // 初始化一个栈s
    push(s, T); // 将根结点T进栈

    while (!StackEmpty(s)) { // 当栈不为空时循环
        while (GetTop(s, p) && p) { // 取栈顶元素p,并且p不为空
            push(s, p->lchild); // 将p的左子结点进栈
        }

        pop(s, p); // 栈顶元素出栈
        if (!StackEmpty(s)) { // 如果栈不为空
            pop(s, p); // 再次弹出栈顶元素
            printf("%d ", p->data); // 访问p结点
            push(s, p->rchild); // 将p的右子结点进栈
        }
    }

    return OK;
}

详细讲解

  1. 初始化栈:
InitStack(s); // 初始化一个栈s

首先,我们初始化一个空栈s,用于存储树节点的指针。

  1. 将根结点进栈:
push(s, T); // 将根结点T进栈

将根节点T指针压入栈中,开始遍历。

  1. 外层循环:当栈不为空时循环:
while (!StackEmpty(s)) { // 当栈不为空时循环

只要栈不为空,就继续循环。这个循环确保了所有节点都会被访问。

  1. 内层循环:处理当前节点的左子树:
while (GetTop(s, p) && p) { // 取栈顶元素p,并且p不为空
  push(s, p->lchild); // 将p的左子结点进栈
}
  • GetTop(s, p):取栈顶元素并将其赋值给p。如果栈顶元素不为空,则继续循环。

  • push(s, p->lchild):将当前节点p的左子节点压入栈中。

内层循环的目的是不断深入左子树的最左端,将路径上的所有节点压入栈中。

  1. 出栈处理节点:
pop(s, p); // 栈顶元素出栈
if (!StackEmpty(s)) { // 如果栈不为空
  pop(s, p); // 再次弹出栈顶元素
  printf("%d ", p->data); // 访问p结点
  push(s, p->rchild); // 将p的右子结点进栈
}
  • pop(s, p):弹出栈顶元素,返回给p

  • if (!StackEmpty(s)):检查栈是否为空,如果不为空则继续处理。

  • pop(s, p):再次弹出栈顶元素。这是因为在前一个pop操作后,栈顶可能是一个NULL指针,我们需要弹出实际的节点。

  • printf("%d ", p->data):访问并打印当前节点的数据。

  • push(s, p->rchild):将当前节点的右子节点压入栈中。

上述步骤处理了一个节点并将其右子树的根节点压入栈中,准备下一次遍历。

通过这个算法,我们实现了非递归的中序遍历。在中序遍历中,访问顺序是左子树 -> 根节点 -> 右子树。栈的使用帮助我们记录路径并回溯到正确的节点,确保遍历顺序正确。


图解

下面是一个示例二叉树以及其非递归中序遍历过程的图解:

示例二叉树

       1
      / \
     2   3
    / \
   4   5

非递归中序遍历过程

我们用栈来帮助进行中序遍历,访问顺序应该是:4, 2, 5, 1, 3。

初始状态:
  • s 为空

  • 根节点 1

Stack: []
Current node: 1
步骤 1:将根节点及其左子节点压入栈
  • 压入节点 1

  • 当前节点变为 1 的左子节点 2

Stack: [1]
Current node: 2
  • 压入节点 2

  • 当前节点变为 2 的左子节点 4

Stack: [1, 2]
Current node: 4
  • 压入节点 4

  • 当前节点变为 4 的左子节点(NULL

Stack: [1, 2, 4]
Current node: NULL
步骤 2:处理左子节点为空的情况
  • 弹出节点 4 并访问
Visited: 4
Stack: [1, 2]
  • 将当前节点变为 4 的右子节点(NULL
Current node: NULL
步骤 3:继续回溯到上一个节点
  • 弹出节点 2 并访问
Visited: 2
Stack: [1]
  • 将当前节点变为 2 的右子节点 5
Current node: 5
  • 压入节点 5

  • 当前节点变为 5 的左子节点(NULL

Stack: [1, 5]
Current node: NULL
步骤 4:处理右子节点
  • 弹出节点 5 并访问
Visited: 5
Stack: [1]
  • 将当前节点变为 5 的右子节点(NULL
Current node: NULL
步骤 5:回溯到根节点
  • 弹出节点 1 并访问
Visited: 1
Stack: []
  • 将当前节点变为 1 的右子节点 3
Current node: 3
  • 压入节点 3

  • 当前节点变为 3 的左子节点(NULL

Stack: [3]
Current node: NULL
步骤 6:处理最后一个节点
  • 弹出节点 3 并访问
Visited: 3
Stack: []
  • 当前节点变为 3 的右子节点(NULL
Current node: NULL
最终结果

访问顺序为:4, 2, 5, 1, 3

每一步操作通过栈的状态和当前节点的变化,保证了中序遍历的顺序。


非递归方法交换左右孩子

要完成将二叉树中左、右孩子交换的非递归算法,可以参考以下详细实现:

#include <stdio.h>

typedef struct node {
    int data;
    struct node *lchild, *rchild;
} Node, *Tree;

void exchange(Tree t) {
    Tree r, p;
    Tree stack[500];
    int tp = 0;
    
    // (1) 初始化堆栈,将根节点放入堆栈
    stack[tp] = t;
    
    while (tp >= 0) {
        // (2) 取出栈顶元素
        p = stack[tp--];
        
        if (p != NULL) {
            // 交换p的左、右子树
            r = p->lchild;
            p->lchild = p->rchild;
            p->rchild = r;
            
            // 将p的左右子节点分别入栈
            stack[++tp] = p->lchild;
            stack[++tp] = p->rchild;
        }
    }
}

// 辅助函数用于创建新节点
Tree createNode(int data) {
    Tree newNode = (Tree)malloc(sizeof(Node));
    newNode->data = data;
    newNode->lchild = newNode->rchild = NULL;
    return newNode;
}

// 辅助函数用于中序遍历打印树
void inorder(Tree t) {
    if (t != NULL) {
        inorder(t->lchild);
        printf("%d ", t->data);
        inorder(t->rchild);
    }
}

int main() {
    // 创建一个简单的二叉树
    Tree root = createNode(1);
    root->lchild = createNode(2);
    root->rchild = createNode(3);
    root->lchild->lchild = createNode(4);
    root->lchild->rchild = createNode(5);
    
    printf("Original tree (inorder): ");
    inorder(root);
    printf("\n");
    
    exchange(root);
    
    printf("Tree after exchanging children (inorder): ");
    inorder(root);
    printf("\n");
    
    return 0;
}

具体步骤如下:

  1. 初始化堆栈
stack[tp] = t;

将根节点放入堆栈。

  1. 循环遍历堆栈
while (tp >= 0) {
  p = stack[tp--];

当堆栈不为空时,取出栈顶元素p

  1. 交换左右子树
if (p != NULL) {
  r = p->lchild;
  p->lchild = p->rchild;
  p->rchild = r;

如果当前节点p不为空,交换它的左、右子树。

  1. 将左右子树入栈
stack[++tp] = p->lchild;
stack[++tp] = p->rchild;

将当前节点p的左、右子树分别入栈。

通过上述步骤实现了对二叉树的左右子树的交换。通过非递归方法和使用堆栈,避免了递归带来的栈溢出风险。


广义表建立二叉树

在这里插入图片描述


广义表建立二叉树的算法

#include <iostream>
#include <stack>
#include <sstream>

using namespace std;

struct BinTreeNode {
    char data;
    BinTreeNode* leftchild;
    BinTreeNode* rightchild;
};

void CreateBinTree(BinTreeNode* &BT, const string& ls) {
    stack<BinTreeNode*> s;
    while (!s.empty()) s.pop(); // MakeEmpty(s):置空栈
    BT = NULL; // 置空二叉树
    BinTreeNode* p = NULL;
    int k = 0; 
    istringstream ins(ls); // 把串ls定义为输入字符串流对象ins
    char ch; 
    ins >> ch; // 从ins顺序读入一个字符
    
    while (ch != '#') { // 逐个字符处理,直到遇到‘#’为止
        switch (ch) {
            case '(': 
                s.push(p); // (1) 当前结点进栈
                k = 1; 
                break;
                
            case ')': 
                s.pop(); // Pop(s):退栈
                break;
                
            case ',': 
                k = 2; // (2) 切换到处理右孩子
                break;
                
            default: 
                p = new BinTreeNode;
                p->data = ch; // 设置结点数据
                p->leftchild = NULL;
                p->rightchild = NULL;
                
                if (BT == NULL) {
                    BT = p; // (4) 置根结点
                } else if (k == 1) {
                    s.top()->leftchild = p; // 处理左孩子
                } else {
                    s.top()->rightchild = p; // 处理右孩子
                }
                break;
        }
        ins >> ch; // (5) 读入下一个字符
    }
}

// 辅助函数用于中序遍历打印树
void inorder(BinTreeNode* t) {
    if (t != NULL) {
        inorder(t->leftchild);
        cout << t->data << " ";
        inorder(t->rightchild);
    }
}

int main() {
    BinTreeNode* BT;
    string ls = "A(B(D,E(H,I)),C(,F(G,)))#";
    CreateBinTree(BT, ls);
    
    cout << "Inorder traversal of the constructed tree: ";
    inorder(BT);
    cout << endl;
    
    return 0;
}

具体填补的地方如下:

  1. 当前结点进栈
s.push(p);

在遇到左括号 ( 时,将当前节点 p 压入栈中。

  1. 切换到处理右孩子
k = 2;

在遇到逗号 , 时,切换到处理右孩子。

  1. 弹出栈顶元素
s.pop();

在遇到右括号 ) 时,弹出栈顶元素。

  1. 设置根结点
BT = p;

如果树为空,则当前节点 p 为根结点。

  1. 读入下一个字符
ins >> ch;

在每次处理完一个字符后,读入下一个字符。

通过这些步骤,我们可以从广义表的字符串表示构建二叉树,并通过中序遍历验证树的结构是否正确。


中序线索树遍历

要完成中序线索树的遍历算法,首先需要理解线索树的结构和遍历方法。以下是该算法的完整实现及详细解释:

中序线索树遍历算法

void inorderthread(BinTreeNode* thr) {
    BinTreeNode* p = thr->lchild;

    while (p != thr) { // (1) p不等于头结点时循环
        while (p->ltag == 0) // (2) p的左标志域为0时循环
            p = p->lchild; // (3) p指向其左孩子

        printf("%c ", p->data); // 打印p的数据

        while (p->rtag == 1 && p->rchild != thr) { // (4) p的右标志域为1且p的右孩子不为头结点时循环
            p = p->rchild;
            printf("%c ", p->data); // 打印p的数据
        }

        p = p->rchild; // (5) p指向其右孩子
    }
}

代码说明

  1. 初始化:
BinTreeNode* p = thr->lchild;

从头结点的左孩子开始(即实际的树的根结点)。

  1. 外层循环:
while (p != thr) {

p不等于头结点时循环。遍历整个树。

  1. 内层循环:
while (p->ltag == 0)
  p = p->lchild;

如果p的左标志域ltag为0,则p指向其左孩子,一直找到最左节点。

  1. 打印数据:
printf("%c ", p->data);

打印当前节点的数据。

  1. 处理线索化右子树:
while (p->rtag == 1 && p->rchild != thr) {
  p = p->rchild;
  printf("%c ", p->data);
}

如果p的右标志域rtag为1,且p的右孩子不为头结点,则通过右线索访问后继节点并打印其数据。

  1. 移动到右孩子:
p = p->rchild;

最后,p指向其右孩子,继续循环。

示例二叉树

假设我们有以下中序线索化的二叉树:

       A
      / \
     B   C
    /   / \
   D   E   F

其线索化过程略。

遍历过程

  1. 从头结点的左孩子(即A)开始。

  2. 沿左子树找到最左节点(即D)。

  3. 打印D。

  4. 处理D的右线索,找到B,打印B。

  5. 处理B的右孩子(即A),打印A。

  6. 沿右子树找到E,打印E。

  7. 处理E的右线索,找到C,打印C。

  8. 沿右子树找到F,打印F。

整个遍历过程依次打印:D, B, A, E, C, F。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值