一步一步写算法(之排序二叉树删除-1)

原文:一步一步写算法(之排序二叉树删除-1)

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】

    相比较节点的添加,平衡二叉树的删除要复杂一些。因为在删除的过程中,你要考虑到不同的情况,针对每一种不同的情况,你要有针对性的反应和调整。所以在代码编写的过程中,我们可以一边写代码,一边写测试用例。编写测试用例不光可以验证我们编写的代码是否正确,还能不断提高我们开发代码的自信心。这样,即使我们在开发过程对代码进行修改或者优化也不会担心害怕。然而看起来编写测试用例是一个繁杂的过程,但是从长期的收益来看,编写测试用例的成本是非常低廉的。

    在排序二叉树的删除过程当中,我们应该怎么做呢?大家不用担心,只要按照我们下面的介绍一步一步往下做就可以了,大体上分为下面三个步骤:

    1)判断参数的合法性,判断参数是否在当前的二叉树当中

    2)删除的节点是根节点,此时应该怎么调整

    3)删除的节点是普通节点,此时又应该怎么调整

    闲话不多说,下面看看我们的代码是怎么设计的?

   1、判断参数的合法性,同时判断当前的二叉树是否含有相关数据

    1.1 判断输入参数是否合法

STATUS delete_node_from_tree(TREE_NODE** ppTreeNode, int data)
{
	if(NULL == ppTreeNode || NULL == *ppTreeNode)
		return FALSE;
	return TRUE;
}

    那么此时测试用例怎么写呢?

static void test1()
{
	TREE_NODE* pTreeNode = NULL;
	assert(FALSE == delete_node_from_tree(NULL, 10));
	assert(FALSE == delete_node_from_tree(&pTreeNode, 10));
}

    注: 上面的测试用例说明当指针为空或者指针的指针为空,函数返回FALSE。

    1.2 判断输入数据是否存在

STATUS delete_node_from_tree(TREE_NODE** ppTreeNode, int data)
{
	TREE_NODE* pTreeNode;

	if(NULL == ppTreeNode || NULL == *ppTreeNode)
		return FALSE;

	pTreeNode = find_data_in_tree_node(*ppTreeNode, data);
	if(NULL == pTreeNode)
		return FALSE;

	return TRUE;
}

    此时,我们设计一种当前指针合法,但是删除数据不存在的测试用例。

static void test2()
{
	TREE_NODE* pTreeNode = NULL;
	pTreeNode = create_tree_node(10);
	assert(FALSE == delete_node_from_tree(&pTreeNode, 11));
	free(pTreeNode);
}

    注: 上面的测试用例根节点为10,但是删除的数据为11,单步跟踪,验证我们编写的代码是否正确。

    2、删除的数据是根节点数据

    2.1 删除根数据时,根节点没有左子树,没有右子树情形

/*
*
*         10          ======>    NULL
*        /  \
*      NULL  NULL
*/

    那么此时代码应该怎么写呢?我们可以试一试。

STATUS delete_node_from_tree(TREE_NODE** ppTreeNode, int data)
{
	TREE_NODE* pTreeNode;

	if(NULL == ppTreeNode || NULL == *ppTreeNode)
		return FALSE;

	pTreeNode = find_data_in_tree_node(*ppTreeNode, data);
	if(NULL == pTreeNode)
		return FALSE;

	if(*ppTreeNode == pTreeNode){
		if(NULL == pTreeNode->left_child && NULL == pTreeNode->right_child){
			*ppTreeNode = NULL;
		}

		free(pTreeNode);
		return TRUE;
	}

	return TRUE;
}

    我们的代码明显越来越长,我们要保持耐心。此时,该是我们添加新测试用例的时候了。

static void test3()
{
	TREE_NODE* pTreeNode = NULL;
	pTreeNode = create_tree_node(10);
	assert(TRUE == delete_node_from_tree(&pTreeNode, 10));
	assert(NULL == pTreeNode);
}

    2.2 删除根数据时,只有左子树节点,没有右子树节点

/*
*
*         10          ======>    5
*        /  \                  /  \
*      5  NULL                3    NULL
*     /
*    3
*/

    很明显,我们只需要把用左子树节点代替原来的根节点即可。

STATUS delete_node_from_tree(TREE_NODE** ppTreeNode, int data)
{
	TREE_NODE* pTreeNode;

	if(NULL == ppTreeNode || NULL == *ppTreeNode)
		return FALSE;

	pTreeNode = find_data_in_tree_node(*ppTreeNode, data);
	if(NULL == pTreeNode)
		return FALSE;

	if(*ppTreeNode == pTreeNode){
		if(NULL == pTreeNode->left_child && NULL == pTreeNode->right_child){
			*ppTreeNode = NULL;
		}else if(NULL != pTreeNode->left_child && NULL == pTreeNode->right_child){
			*ppTreeNode = pTreeNode->left_child;
			pTreeNode->left_child->parent = NULL;
		}

		free(pTreeNode);
		return TRUE;
	}

	return TRUE;
}

    这个时候,我们可以添加新的测试用例,分别添加10、5、3,然后删除10。

static void test4()
{
	TREE_NODE* pTreeNode = NULL;
	assert(TRUE == insert_node_into_tree(&pTreeNode, 10));
	assert(TRUE == insert_node_into_tree(&pTreeNode, 5));
	assert(TRUE == insert_node_into_tree(&pTreeNode, 3));
	assert(TRUE == delete_node_from_tree(&pTreeNode, 10));
	assert(5 == pTreeNode->data);
	assert(NULL == pTreeNode->parent);
	free(pTreeNode->left_child);
	free(pTreeNode);
}

    2.3 删除根数据时,没有左子树节点,只有右子树节点

/*
*
*         10          ======>    15
*        /  \                   /   \
*     NULL  15               NULL    20
*             \
*             20
*/

    上面的代码表示了节点的删除过程。我们可以按照这个流程编写代码。

STATUS delete_node_from_tree(TREE_NODE** ppTreeNode, int data)
{
	TREE_NODE* pTreeNode;

	if(NULL == ppTreeNode || NULL == *ppTreeNode)
		return FALSE;

	pTreeNode = find_data_in_tree_node(*ppTreeNode, data);
	if(NULL == pTreeNode)
		return FALSE;

	if(*ppTreeNode == pTreeNode){
		if(NULL == pTreeNode->left_child && NULL == pTreeNode->right_child){
			*ppTreeNode = NULL;
		}else if(NULL != pTreeNode->left_child && NULL == pTreeNode->right_child){
			*ppTreeNode = pTreeNode->left_child;
			pTreeNode->left_child->parent = NULL;
		}else if(NULL == pTreeNode->left_child && NULL != pTreeNode->right_child){
			*ppTreeNode = pTreeNode->right_child;
			pTreeNode->right_child->parent = NULL;
		}

		free(pTreeNode);
		return TRUE;
	}

	return TRUE;
}

    添加测试用例,依次添加10、15、20,然后删除数据10。

static void test5()
{
	TREE_NODE* pTreeNode = NULL;
	assert(TRUE == insert_node_into_tree(&pTreeNode, 10));
	assert(TRUE == insert_node_into_tree(&pTreeNode, 15));
	assert(TRUE == insert_node_into_tree(&pTreeNode, 20));
	assert(TRUE == delete_node_from_tree(&pTreeNode, 10));
	assert(15 == pTreeNode->data);
	assert(NULL == pTreeNode->parent);
	free(pTreeNode->right_child);
	free(pTreeNode);
}

    2.4删除数据的左右节点都存在

   (待续)

时间: 2024-10-30 01:43:48

一步一步写算法(之排序二叉树删除-1)的相关文章

一步一步写算法(之排序二叉树删除-2)

原文:一步一步写算法(之排序二叉树删除-2) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     2.4 删除节点的左右子树都存在,此时又会分成两种情形     1)左节点是当前左子树的最大节点,此时只需要用左节点代替根节点即可 /* * * 10 ======> 6 * / \ / \ * 6 15 5 15 * / * 5 */     代码该怎么编写呢? STATUS delete_node_from_tree(TREE_NOD

一步一步写算法(之排序二叉树删除-3)

原文:一步一步写算法(之排序二叉树删除-3) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     3 普通节点的删除     3.1 删除的节点没有左子树,也没有右子树      测试用例1: 删除节点6 /* * * 10 ======> 10 * / \ \ * 6 15 15 * */ static void test8() { TREE_NODE* pTreeNode = NULL; assert(TRUE == insert

一步一步写算法(之二叉树深度遍历)

原文:一步一步写算法(之二叉树深度遍历) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     深度遍历是软件开发中经常遇到的遍历方法.常用的遍历方法主要有下面三种:(1)前序遍历:(2)中序遍历:(3)后序遍历.按照递归的方法,这三种遍历的方法其实都不困难,前序遍历就是根-左-右,中序遍历就是左-根-右,后续遍历就是左-右-根.代码实现起来也不复杂.     1)前序遍历 void preorder_traverse(TREE_NOD

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

原文:一步一步写算法(之排序二叉树线索化) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     前面我们谈到了排序二叉树,还没有熟悉的同学可以看一下这个,二叉树基本操作.二叉树插入.二叉树删除1.删除2.删除3.但是排序二叉树也不是没有缺点,比如说,如果我们想在排序二叉树中删除一段数据的节点怎么办呢?按照现在的结构,我们只能一个一个数据查找验证,首先看看在不在排序二叉树中,如果在那么删除:如果没有这个数据,那么继续查找.那么有没有方法

一步一步写算法(之非递归排序)

原文:一步一步写算法(之非递归排序) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]        在上面一篇博客当中,我们发现普通查找和排序查找的性能差别很大.作为一个100万的数据,如果使用普通的查找方法,那么每一个数据查找平均下来就要几十万次,那么二分法的查找呢,20多次就可以搞定.这中间的差别是非常明显的.既然排序有这么好的效果,那么这篇博客中,我们就对排序算做一个总结.     按照我个人的理解,排序可以分为两种:一种是非递归排

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

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

一步一步写算法(之排序二叉树的保存和加载)

原文:一步一步写算法(之排序二叉树的保存和加载)[ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     排序二叉树是我们开发中经常使用到的一种数据结构,它具有较好的插入.删除.查找特性.但是由于二叉树的指针较多,所以相比较其他的数据结构而言,二叉树来得比较麻烦些.但是也不是没有办法,下面介绍一下我个人常用的方法.     我们知道,如果一个二叉树是一个满树的话,那么二叉树的节点应该是按照1.2.3.4依次排开的.但是现实情况是这样的,由于

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

原文:一步一步写算法(之排序二叉树插入) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     二叉树的节点插入比较简单.一般来说,二叉树的插入主要分为以下两个步骤:     1) 对当前的参数进行判断,因为需要考虑到头结点,所以我们使用了指针的指针作为函数的输入参数     2) 分情况讨论:         如果原来二叉树连根节点都没有,那么这个新插入的数据就是根节点:         如果原来的二叉树有根节点,那我们判断这个数据是

一步一步写算法(之链表排序)

原文:一步一步写算法(之链表排序) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     相比较线性表的排序而言,链表排序的内容稍微麻烦一点.一方面,你要考虑数据插入的步骤:另外一方面你也要对指针有所顾虑.要是有一步的内容错了,那么操作系统会马上给你弹出一个exception.就链表的特殊性而言,适合于链表的排序有哪些呢?     (1)插入排序    (适合)     (2)冒泡排序    (适合)     (3)希尔排序    (适