双向链表(4) - 排序二叉树转换为循环双向链表

构建一个递归函数treeToList(Node root),将一棵已排序的二叉树,调整内部指针,使之从外面看起来,是一个循环双向链表。其中前向指针存储在"small"区域,后向指针存储在"large"区域。链表需要进行调整进行升序排序,并返回链表头指针。

下面的这篇文章详细解释了这个转换的过程。

http://cslibrary.stanford.edu/109/TreeListRecursion.html

以下是对这篇文章的翻译:

The Great Tree-List Recursion Problem

内容:

1. Ordered binary tree

2. Circular doubly linked list

3. The Challenge

4. Problem Statement

5. Lessons and Solution Code

介绍:

此问题会使用到两个数据结构 -- 一个已排序的二叉树,以及一个循环双向链表。这两者存储的都是已排序的元素,但是看上去完全不同。

1. 排序二叉树(Ordered Binary Tree)

在一棵已排序的二叉树中,每个节点包含一个独立的数据元素和指向子树的"small"以及"large"指针(有时这两个指针称为"left"左指针和“right”右指针)。下面所示的是一棵已排序的二叉树,包含元素1到5.

图1 - 已排序的二叉树

其中,"small"子树中的所有节点,小于等于父节点。"large"子树中的所有节点,大于父节点。所以在上述例子中,"small"子树中的节点都会小于等于4,而"large"子树中的所有节点,会大于4. 这条规则对树中的每个节点都适用。而null指针,用来表示一个分支的结束。实际上,null指针代表0个节点的树。树中最顶点的节点称为根节点root。

2. 循环双向链表(Circular Doubly Linked List)

下面是1至5的一个循环双向链表

图2 - 循环双向链表

循环双向链表相对普通链表,拥有下面两个额外的特性:

a)."双向"代表每个节点拥有两个指针- 后向指针"next"以及前向指针"previous"。

b)."循环"代表链表没有起点和终点。实际上,最后一个节点的后向指针,指向的是首节点。而首节点的前向指针,指向的是最后一个节点。

通常,一个null指针代表一个0个元素的链表。而长度为1的链表看起来会有点奇怪。如下面所示:

图3 - 长度为1的循环双向链表

长度为1的链表中的那个节点,既是首节点,也是最后一个节点(末节点),所以前向指针和后向指针,指向的都是它自己。所以即使长度为1的链表,也遵守了上面的特性。

本质 - 节点在构造时就不同

这里就是Great Tree-List问题的本质:二叉树中的节点,与链表中的节点,都有着相同的类型结构 - 它们都包含了一个元素和两个指针。唯一的区别在于,二叉树中的两个指针标识为"small"和"large",而链表中标识为"previous"和"next"。忽略掉标识后,这两个节点的类型其实是一样的。

3.难题

真正的困难在于将此二叉树转换成循环双向链表。"small"指针代表"previous",而"large"指针代表"next"。转换完成后,需要调整链表来达到升序排序。

图4 - 添加了后向指针(next)后的二叉树

上面的这个图中,黑色线条表示原始二叉树中的后向指针,即链表中用箭头表示的。前向指针没有进行演示。

完整的调整演示:

图5 - 添加了前向指针与后向指针的二叉树

上面的这个图,演示了这个问题的所有情况。原始二叉树使用黑色线条,并且给二叉树添加了后向/前向指针。注意头指针的位置,它的前向指针与后向指针,构成了一个包含元素1~5的链表,如图2所示。尽管这两幅图中每个节点有着不同的空间分配,但这只是它们的数据结构属性的差异。指针结构才是关键。

4. 问题总结

构建一个递归函数treeToList(Node root),将一棵已排序的二叉树,调整内部指针,使之从外面看起来,是一个循环双向链表。其中前向指针存储在"small"区域,后向指针存储在"large"区域。链表需要进行调整进行升序排序,并返回链表头指针。

操作可以在O(n)时间完成 - 因为本质上只需在每个节点上操作一次。通常将图1做为输入,调整指针后,生成图2.

5. 具体实现的一些提示

a). 递归是关键。对每颗子树进行递归,并合并每次递归的返回结果。

b). 递归会逐步把small和large子树,调整为链表。然后把所有的这些链表合并成一个完整链表。单独定义一个函数append(Node a, Node b)来接收两个链表的输入,然后返回合并后的链表。独立出来的这个函数,也会减小递归函数中的逻辑复杂度。

6. 代码实现

#include <iostream>

struct Node
{
	int data;
	struct Node *small;
	struct Node *large;
};

//辅助函数-合并两个节点。
void joinNode(Node* a, Node * b)
{
	/*
	a
	 	  	   b
	*/
	a->large = b;
	b->small = a;
}

//辅助函数 - 合并两个循环双向链表
Node* appendList(Node *a, Node *b)
{
	Node *aLast, *bLast;
	if (a == NULL)
		return b;
	if (b == NULL)
		return a;

	aLast = a->small;
	bLast = b->small;

	joinNode(aLast, b);
	joinNode(bLast, a);

	return a;
}

//递归函数
//将一棵已排序二叉树转换为一个循环双向链表,并返回链表。
Node* treeToList(Node *root)
{
	Node *aList, *bList;

	if (root == NULL)
		return NULL;

	aList = treeToList(root->small);
	bList = treeToList(root->large);

	//构造一个长度为length-1的链表
	root->small = root;
	root->large = root;

	aList = appendList(aList, root);
	aList = appendList(aList, bList);

	return aList;
}

Node* createNode(int data)
{
	Node * node = new Node;
	node->data = data;
	node->small = NULL;
	node->large = NULL;
	return node;
}

void insertNode(Node **rootRef, int data)
{
	Node *root = *rootRef;
	if (root == NULL)
		*rootRef = createNode(data);
	else
	{
		if (data <= root->data)
			insertNode(&(root->small), data);
		else
			insertNode(&(root->large), data);
	}
}

void printList(const Node *head)
{
	const Node *current = head;
	while (current != NULL)
	{
		std::cout << current->data << " ";
		current = current->large;
		if (current == head)   //已完成了一次链表遍历
			break;
	}
	std::cout << std::endl;
}

int main()
{
	Node *root = NULL, *head = NULL;

	//4-->2-->1-->3-->5
	insertNode(&root, 4);
	insertNode(&root, 2);
	insertNode(&root, 1);
	insertNode(&root, 3);
	insertNode(&root, 5);

	head = treeToList(root);

	printList(head);

	return 0;
}

输出:

1 2 3 4 5

时间: 2024-09-29 03:13:13

双向链表(4) - 排序二叉树转换为循环双向链表的相关文章

数据结构——树——二叉查找树转换成排序的循环双向链表

题目描述 输入一棵二叉查找树,将该二叉查找树转换成一个排序的循环双向链表. 要求不能创建任何新的结点,只调整指针的指向,也不能开辟新的存储空间O(1) 题目分析 首先照旧我们问题的解决思路,首先简化问题.(前提你应该了解二叉查找树和双向链表) 如果没有任何的要求,那么我们自然会想到遍历整棵树然后排序出来之后重新构建一个链表即可,你想要什么样的都可以. 那么我们需要考虑的就是如何遍历? 然后慢慢复杂问题,不能新建立,那么就要在原来的基础上改,怎么改才能变过去呢? 这时,我要教你对于树的问题,有一招

算法题——二叉树转换为双向链表

1 BSTreeNode* ConvertNode(BSTreeNode* pNode, bool asRight) 2 { 3 if(!pNode) 4 return NULL; 5 6 BSTreeNode *pLeft = NULL; 7 BSTreeNode *pRight = NULL; 8 9 // Convert the left sub-tree 10 if(pNode->m_pLeft) 11 pLeft = ConvertNode(pNode->m_pLeft, false

二叉搜索树转换为有序双向链表

http://blog.csdn.net/ljianhui/article/details/22338405 一.问题描述 输入一棵二叉搜索树,现在要将该二叉搜索树转换成一个排序的双向链表.而且在转换的过程中,不能创建任何新的结点,只能调整树中的结点指针的指向来实现. 二.实现思路 在二叉搜索树中,每个结点都有两个分别指向其左.右子树的指针,左子树结点的值总是小于父结点的值,右子树结点的值总是大于父结点的值.而在双向链表中,每个结点也有两个指针,它们分别指向前一个结点和后一个结点.所以这两种数据

经典算法学习——非循环双向链表实现冒泡排序(不带头结点)

我在前面两篇博客<经典算法学习--单链表(不带头结点)实现冒泡排序><经典算法学习--单链表实现冒泡排序(带头结点)>中详细描述了分别使用带头结点和不带头结点的单链表实现了冒泡排序,让我们对单链表和冒泡排序有了理性的认识.今天我们将会来使用不带头结点的非循环双向链表来实现冒泡排序,在处理过程中,这种冒泡比前面两种更为简单高效.代码上传至 https://github.com/chenyufeng1991/DoubleLinkedList_BubbleSort . 核心代码如下: /

循环双向链表的C++实现

循环双向链表的增删查改等基本操作 #include<iostream> #include<assert.h> using namespace std; typedef int DataType; struct ListNode { DataType _data; ListNode* _prev; ListNode* _next; ListNode(const DataType& x) :_data(x) ,_prev(NULL) ,_next(NULL) {} ListNo

栈和队列----将搜索二叉树转换成双向链表

将搜索二叉树转换成双向链表 对于BST 来说,有本身的值域,有指向左孩子和右孩子的两个指针:对于双向链表来说,有本身的值域,有指向上一个节点和下一个节点的指针.将这个BST转换成双向链表,对于每一个节点来说,原来的right指针等价于转换后的next指针,原来的left指针等价于转换后的left指针,最后返回双向链表的头节点. [解析] 用队列等容器收集二叉树 中序遍历的结果的方法.其时间复杂度是O(N),空间复杂度是O(N). 1. 生成一个队列,记为queue,按照二叉树的中序遍历的顺序,将

一步一步写算法(之排序二叉树)

原文:一步一步写算法(之排序二叉树) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 前面我们讲过双向链表的数据结构.每一个循环节点有两个指针,一个指向前面一个节点,一个指向后继节点,这样所有的节点像一颗颗珍珠一样被一根线穿在了一起.然而今天我们讨论的数据结构却有一点不同,它有三个节点.它是这样定义的: typedef struct _TREE_NODE { int data; struct _TREE_NODE* parent; str

01-(2)数据结构- 一步一步写算法(之之排序二叉树)

前面我们讲过双向链表的数据结构.每一个循环节点有两个指针,一个指向前面一个节点,一个指向后继节点,这样所有的节点像一颗颗珍珠一样被一根线穿在了一起.然而今天我们讨论的数据结构却有一点不同,它有三个节点.它是这样定义的: [cpp] view plain copy typedef struct _TREE_NODE { int data; struct _TREE_NODE* parent; struct _TREE_NODE* left_child; struct _TREE_NODE* rig

数据结构与算法系列研究五——树、二叉树、三叉树、平衡排序二叉树AVL

树.二叉树.三叉树.平衡排序二叉树AVL 一.树的定义 树是计算机算法最重要的非线性结构.树中每个数据元素至多有一个直接前驱,但可以有多个直接后继.树是一种以分支关系定义的层次结构.    a.树是n(≥0)结点组成的有限集合.{N.沃恩}     (树是n(n≥1)个结点组成的有限集合.{D.E.Knuth})      在任意一棵非空树中:        ⑴有且仅有一个没有前驱的结点----根(root).        ⑵当n>1时,其余结点有且仅有一个直接前驱.         ⑶所有结