vlambda博客
学习文章列表

数据结构 · 二叉树转换为双向链表

1. 二叉树转换为双向链表

1. 1 二叉树节点数据类型声明

      在  数据结构 · 树(二叉树)  一文中,详细的概述了二叉树的各种分类与特性,同时也用数组(顺序存储)方式来实现了二叉树的存储与遍历。显而易见,使用数组方式实现二叉树,视树的情形不同,对内存的空间浪费也大有所异(除非该二叉树是满二叉树),并且,使用数组来实现二叉树,非常具有局限性,这与用数组实现其他数据类型(比如队列、栈等)一样。因此需要使用动态的方式来实现树的存取,即使用链表。
     根据二叉树的定义,它最多拥有两个子节点,因此对于二叉树的节点数据结构的定义如下:
struct TreeNode{ int data; //数据域 TreeNode *left; //左子树 TreeNode *right; //右子树};

从二叉树节点的数据结构声明可得知,它与双向链表节点的结构声明是相同的。二叉树中的left(左子树)指针对应双向链表中的prev(直接前驱)指针,而rigth(右子树)对应双向链表中的next(直接后继)指针

     因此,对于给定的一棵二叉树,通过修改该树节点中的左、右指针,使它们表示双向链接。双向链表(Doubly Linked List, DLL)中节点的顺序必须与二叉树中序遍历的节点顺序相同。在转换过程中,二叉树中的left、right节点将分别作为链表中的prev、next节点。且中序遍历的第一个节点(即二叉树BT最左边的节点)必须是双向链表(DLL)的头节点。如下图中的二叉树中序遍历第一个节点是"D"。

 二叉树中序遍历
若输入待转换为双向链表的二叉树为下图:

数据结构 · 二叉树转换为双向链表

 待转换为DLL的二叉树

则中序遍历后,其节点的顺序是:

D, B, E, A, F, C, G

     按照二叉树“中序遍历(Inorder Traversal)”的规则,通过修改二叉树中节点的指针指向,最终可得到的一个相同顺序的双向链表如下,且该双向链表返回一个头结点指针。

备注:对于DLL中的第一个节点“D”,它即充当投头节点的角色,因此上方的“Head”节点应该去掉。二叉树转换为DLL过程中,用户希望是对等转换的,而不希望附加另外的节点。

数据结构 · 二叉树转换为双向链表

 二叉树转换为双向链表DLL

整个转换过程详细步骤如下:

  • 若左子树存在,则处理左子树
  1. 递归地将左子树转换为双向链表DLL;
  2. 然后在左子树中找到中序遍历规则的根的前驱结点(根中序前驱是左子树中的最右边的结点);
     如下图中紫色标注的节点“E”。

数据结构 · 二叉树转换为双向链表

 二叉树转换为DLL过程
  1. 将中序前驱作为根的前驱(prev),并将根作为中序前驱的next。
  •  若右子树存在,则处理右子树
  1. 递归将右子树转换为双向链表DLL;
  2. 然后在右子树中找到根的中序后继(中序后继节点是右子树中最左边节点) ;即上图中的节点“F”。
  3. 然后使中序后继结点作为根的next,且根(root)作为中序后继结点的prve
  • 找到最左边的节点并返回它(最左边的节点总是转换DLL的头)。

1. 2 示例代码

1. 2.1 初始化二叉树节点

      该函数使用给定的数据初始化TreeNode节点中的数据域,同时使该节点的左、右孩子指针为NULL。
     如下图所示:

数据结构 · 二叉树转换为双向链表

      二叉树初始化节点

/* * fn: CreateBTree * desc: 初始化二叉树节点 * date: 2020/12/19/02:27:08 * author: lixiaogang5 * mail: [email protected] * CSDN: lixiaogang_theanswer*/TreeNode *NewBTreeNode(char iEle){ assert(iEle); TreeNode *bTree = (TreeNode *)calloc(1, sizeof(TreeNode));    assert(bTree);     bTree->data = iEle; bTree->left = bTree->right = NULL; return bTree;}


1. 2.2 转换为DLL过程

     按照上面给出的规则,对创建的二叉树进行对等转换为DLL。转换过程中不应该新增节点,转换成功后,其DLL的头节点为该二叉树按中序遍历时候的第一个节点。
/* * fn: BtreeConvertToDLL * desc: 二叉树转换为双向链表 * date: 2020/12/19/02:42:08 * author: lixiaogang5 * mail: lxiaogang5@gmail.com * CSDN: lixiaogang_theanswer*/void BtreeConvertToDLL(TreeNode *bTree, TreeNode **pHead){ if (bTree == NULL) return; BtreeConvertToDLL(bTree->right, pHead); bTree->right = *pHead;
if (*pHead != NULL) { (*pHead)->left = bTree;    }     *pHead = bTree; BtreeConvertToDLL(bTree->left, pHead);}
     

1. 2.3 打印链表节点值

      遍历一个链表的过程,从头结点(DLL的第一个节点)开始,直到链表的最后一个节点。
     
/* * fn: TraversalBtree * desc: 遍历双向链表 * date: 2020/12/19/02:34:56 * author: lixiaogang5 * mail: [email protected] * CSDN: lixiaogang_theanswer*/void TraversalBtree(const TreeNode *bTree){ assert(bTree); const TreeNode *pHead = bTree; while(pHead) { printf("%c ", pHead->data); pHead = pHead->right; }
return;}

1. 2.4 中序遍历二叉树

      根据中序遍历规则,先访问根的左子树,然后根节点,最后右子树的顺序打印该二叉树中的所有节点。
/** fn: InorderBTree* desc: 中序遍历二叉树* date: 2020/12/19/02:35:15* author: lixiaogang5* mail: lxiaogang5@gmail.com* CSDN: lixiaogang_theanswer*/void InorderBTree(TreeNode *bTree){ if(bTree) { InorderBTree(bTree->left); printf("%c ", bTree->data); InorderBTree(bTree->right); }}

1. 2.5 完整demo

本示例的二叉树中共有7个节点,其中节点A是二叉树根节点,其创建过程如下:
TreeNode *bTRoot = NewBTreeNode('A');bTRoot->left = NewBTreeNode('B');bTRoot->right = NewBTreeNode('C');bTRoot->left->left = NewBTreeNode('D');bTRoot->left->right = NewBTreeNode('E');bTRoot->right->left = NewBTreeNode('F');bTRoot->right->right    = NewBTreeNode('G');

下面3张图为这7句语句的内部详细逻辑。


数据结构 · 二叉树转换为双向链表

  二叉树创建过程1


数据结构 · 二叉树转换为双向链表

  二叉树创建过程2


  二叉树创建完成

 完整的demo如下:

#include <stdio.h>#include <assert.h>
typedef struct TreeNode{ char data; //数据域 TreeNode *left; //左子树 TreeNode *right; //右子树}TreeNode;/* * fn: BtreeConvertToDLL * desc: 二叉树转换为双向链表 * date: 2020/12/19/02:42:08 * author: lixiaogang5 * mail: lxiaogang5@gmail.com * CSDN: lixiaogang_theanswer*/void BtreeConvertToDLL(TreeNode *bTree, TreeNode **pHead){ if (bTree == NULL) return; BtreeConvertToDLL(bTree->right, pHead); bTree->right = *pHead;
if (*pHead != NULL) { (*pHead)->left = bTree; }
*pHead = bTree; BtreeConvertToDLL(bTree->left, pHead);}

/* * fn: CreateBTree * desc: 初始化二叉树节点 * date: 2020/12/19/02:27:08 * author: lixiaogang5 * mail: lxiaogang5@gmail.com * CSDN: lixiaogang_theanswer*/TreeNode *NewBTreeNode(char iEle){ TreeNode *bTree = (TreeNode *)calloc(1, sizeof(TreeNode)); assert(bTree);
bTree->data = iEle; bTree->left = bTree->right = NULL; return bTree;}/* * fn: TraversalBtree * desc: 遍历双向链表 * date: 2020/12/19/02:34:56 * author: lixiaogang5 * mail: lxiaogang5@gmail.com * CSDN: lixiaogang_theanswer*/void TraversalBtree(const TreeNode *bTree){ assert(bTree); const TreeNode *pHead = bTree; while(pHead) { printf("%c ", pHead->data); pHead = pHead->right; }
return;}
/** fn: InorderBTree* desc: 中序遍历二叉树* date: 2020/12/19/02:35:15* author: lixiaogang5* mail: lxiaogang5@gmail.com* CSDN: lixiaogang_theanswer*/void InorderBTree(TreeNode *bTree){ if(bTree) { InorderBTree(bTree->left); printf("%c ", bTree->data); InorderBTree(bTree->right); }}
int main(){ //1. 初始化二叉树 TreeNode *bTRoot = NewBTreeNode('A'); bTRoot->left = NewBTreeNode('B'); bTRoot->right = NewBTreeNode('C'); bTRoot->left->left = NewBTreeNode('D'); bTRoot->left->right = NewBTreeNode('E'); bTRoot->right->left = NewBTreeNode('F'); bTRoot->right->right = NewBTreeNode('G');
//2. 中序遍历该二叉树 printf("Inorder Traversal:\n"); InorderBTree(bTRoot);
//3. 二叉树转换为DLL printf("\nConvert BT to DLL:\n"); TreeNode *dLL = NULL; BtreeConvertToDLL(bTRoot, &dLL);
//3.1 遍历DLL TraversalBtree(dLL); return 0;}

打印结果:

 二叉树创建完成

1. 3 运行时复杂度

     线性,O(n)。
     运行时复杂性基于树中的节点数量。

1. 4 内存复杂度

    线性,O(n)。
     递归解决方案具有O(h)的内存复杂性,因为它将消耗堆栈上的内存,直到二叉树“ h”的高度。对于平衡树,它将为O(log n),在最坏的情况下可以为O(n)。