c/c++常用算法(5) -- 数据结构(树)

一、树的定义和基本术语


1.树的定义

      树(Tree)是n(n≧0)个结点的有限集合T,若n=0时称为空树,否则:

            ⑴ 有且只有一个特殊的称为树的根(Root)结点;

            ⑵  若n>1时,其余的结点被分为m(m>0)个互不相交的子集T1, T2, T3…Tm,其中每个子集本身又是一棵树,称其为根的子树(Subtree)。

    这是树的递归定义,即用树来定义树,而只有一个结点的树必定仅由根组成,如图6-1(a)所示。


2树的基本术语

      ⑴ 结点(node):一个数据元素及其若干指向其子树的分支。

      ⑵ 结点的度(degree)、树的度:结点所拥有的子树的棵数称为结点的度。树中结点度的最大值称为树的度。




      如图6-1(b)中结点A的度是3,结点B的度是2,结点M的度是0,树的度是3 。


      ⑶ 叶子(left)结点非叶子结点:树中度为0的结点称为叶子结点(或终端结点)。相对应地,度不为0的结点称为非叶子结点(或非终端结点或分支结点)。除根结点外,分支结点又称为内部结点。

    如图6-1(b)中结点H、I、J、K、L、M、N是叶子结点,而所有其它结点都是分支结点。


      ⑷ 孩子结点双亲结点兄弟结点

    一个结点的子树的根称为该结点的孩子结点(child)或子结点;相应地,该结点是其孩子结点的双亲结点(parent)或父结点。

         如图6-1(b)中结点B、C、D是结点A的子结点,而结点A是结点B、C、D的父结点;类似地结点E、F是结点B的子结点,结点B是结点E、F的父结点。

同一双亲结点的所有子结点互称为兄弟结点

        如图6-1(b)中结点B 、C、D是兄弟结点;结点E、F是兄弟结点。


      ⑸ 层次堂兄弟结点

        规定树中根结点的层次为1,其余结点的层次等于其双亲结点的层次加1。

        若某结点在第l(l≧1)层,则其子结点在第l+1层。

        双亲结点在同一层上的所有结点互称为堂兄弟结点。如图6-1(b)中结点E、F、G、H、I、J。


      ⑹ 结点的层次路径祖先子孙

        从根结点开始,到达某结点p所经过的所有结点成为结点p的层次路径(有且只有一条)。

        结点p的层次路径上的所有结点(p除外)称为p的祖先(ancester)。

        以某一结点为根的子树中的任意结点称为该结点的子孙结点(descent)。


      ⑺树的深度(depth):树中结点的最大层次值,又称为树的高度,如图6-1(b)中树的高度为4。

      ⑻ 有序树和无序树:对于一棵树,若其中每一个结点的子树(若有)具有一定的次序,则该树称为有序树,否则称为无序树

      ⑼森林(forest):是m(m≧0)棵互不相交的树的集合。显然,若将一棵树的根结点删除,剩余的子树就构成了森林。


3  树的表示形式

      ⑴ 倒悬树。是最常用的表示形式,如图6-1(b)。

      ⑵ 嵌套集合。是一些集合的集体,对于任何两个集合,或者不相交,或者一个集合包含另一个集合。图6-2(a)是图6-1(b)树的嵌套集合形式。

      ⑶ 广义表形式。图6-2(b)是树的广义表形式。

      ⑷ 凹入法表示形式


   树的表示方法的多样化说明了树结构的重要性。




二、树的抽象数据类型定义


ADT Tree{
    数据对象D:D是具有相同数据类型的数据元素的集合。
    数据关系R:若D为空集,则称为空树;
    ……
    基本操作:
    ……
} ADT Tree

三、二叉树


1 二叉树的定义

二叉树(Binarytree)n(n0)个结点的有限集合。若n=0时称为空树,否则:

      ⑴有且只有一个特殊的称为树的根(Root)结点;

      ⑵若n>1时,其余的结点被分成为二个互不相交的子集T1,T2,分别称之为左、右子树,并且左、右子树又都是二叉树。

      由此可知,二叉树的定义是递归的。


      二叉树在树结构中起着非常重要的作用。因为二叉树结构简单,存储效率高,树的操作算法相对简单,且任何树都很容易转化成二叉树结构。上节中引入的有关树的术语也都适用于二叉树。


2.二叉树的基本形态


      二叉树有5种基本形态,如图6-3所示




满二叉树的特点

      ◆ 基本特点是每一层上的结点数总是最大结点数。

      ◆ 满二叉树的所有的支结点都有左、右子树。

      ◆ 可对满二叉树的结点进行连续编号,若规定从根结点开始,按“自上而下自左至右”的原则进行。


      完全二叉树(Complete Binary Tree):如果深度为k,由n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应,该二叉树称为完全二叉树。或深度为k的满二叉树中编号从1到n的前n个结点构成了一棵深度为k的完全二叉树。

其中 2k-1 ≦n≦2k-1 。

      完全二叉树是满二叉树的一部分,而满二叉树是完全二叉树的特例。


完全二叉树的特点

       若完全二叉树的深度为k ,则所有的叶子结点都出现在第k层或k-1层。对于任一结点,如果其右子树的最大层次为l,则其左子树的最大层次为l或l+1。


四、实现代码


Tree.h

#pragma once
#include <iostream>
#include <string.h>
#include "stdio.h"
#include <iostream>

using namespace std;

#define MANLEN 20
typedef char DATA;
typedef struct CBT  //定义二叉树的数据结构
{
	DATA data;
	struct CBT *left;
	struct CBT *right;
}CBTType;

class Tree
{
public:
	CBTType *InitTree();                                //初始化
	void AddTreeNode(CBTType *treeNode);                //添加结点
	CBTType *TreeFindNode(CBTType *treeNode,DATA data); //查找结点
	CBTType *TreeLeftNode(CBTType *treeNode);           //获得左子树
	CBTType *TreeRightNode(CBTType *treeNode);          //获得右子树
	int TreeIsEmpty(CBTType *treeNode);                 //判断是否为空树
	int TreeDepth(CBTType *treeNode);                   //计算二叉树深度
	void ClearTree(CBTType *treeNode);                  //清空二叉树
	void TreeNodeData(CBTType *p);                      //显示结点数据
	void LevelTree(CBTType *treeNode,void (*TreeNodeData)(CBTType *p));//按层遍历
	void DLRTree(CBTType *treeNode,void (*TreeNodeData)(CBTType *p));  //先序遍历
	void LDRTree(CBTType *treeNode,void (*TreeNodeData)(CBTType *p));  //中序遍历
	void LRDTree(CBTType *treeNode,void (*TreeNodeData)(CBTType *p));  //后序遍历
};
Tree.cpp

#include "queue.h"
#include <conio.h>


CBTType *Tree::InitTree()
{
	CBTType *node;

	if (node = (CBTType*)malloc(sizeof(CBTType)))
	{
		std::cout<<"请先输入一个根结点数据:\n";
		scanf("%c",&node->data);
		node->left = NULL;
		node->right = NULL;
		if (node != NULL)
		{
			return node;
		}
		else
		{
			return NULL;
		}
	}
	return NULL;
}

void Tree::AddTreeNode(CBTType *treeNode)
{
	CBTType *pnode,*parent;
	char data;
	char menusel;

	if (pnode = (CBTType*)malloc(sizeof(CBTType)))
	{
		std::cout<<"输入二叉树结点数据:\n";
		fflush(stdin);
		scanf("%c",&pnode->data);
		pnode->left = NULL;
		pnode->right = NULL;

		std::cout<<"输入该结点的父结点数据:\n";
		fflush(stdin);
		scanf("%c",&data);

		parent = TreeFindNode(treeNode, data);

		if (!parent)
		{
			std::cout<<"未找到该父结点!\n";
			free(pnode);
			return;
		}
		std::cout<<"1.添加该结点到左子树\n2.添加该结点到右子树\n";

		do
		{
			//scanf("%s",&menusel);
			menusel = getch();
			menusel -='0';
			if (menusel == 1 || menusel == 2)
			{
				if (parent == NULL)
				{
					std::cout<<"不存在父结点,请先设置父结点!\n";
				}
				else
				{
					switch (menusel)
					{
					case 1:
						if (parent->left)
						{
							std::cout<<"左子树结点不为空!\n";
						}
						else
						{
							parent->left = pnode;
						}
						break;

					case 2:
						if (parent->right)
						{
							std::cout<<"右子树结点不为空!\n";
						}
						else
						{
							parent->right = pnode;
						}
						break;   
					default:
						std::cout<<"无效参数!\n";
					}
				}
			}
		} while (menusel != 1 && menusel != 2);
	}
}


CBTType *Tree::TreeFindNode(CBTType *treeNode, DATA data)
{
	CBTType *ptr;

	if (treeNode == NULL)
	{
		return NULL;
	}
	else
	{
		if (treeNode->data == data)
		{
			return  treeNode;
		}
		else
		{
			if (ptr = TreeFindNode(treeNode->left, data))
			{
				return ptr;
			}
			else if(ptr =TreeFindNode(treeNode->right, data))
			{
				return ptr;
			}
			else
			{
				return NULL;
			}   
		}
	}
}


CBTType *Tree::TreeLeftNode(CBTType *treeNode)
{
	if (treeNode)
	{
		return treeNode->left;
	}
	else
	{
		return NULL;
	}
}

CBTType *Tree::TreeRightNode(CBTType *treeNode)
{
	if (treeNode)
	{
		return treeNode->right;
	}
	else
	{
		return NULL;
	}
}

int Tree::TreeIsEmpty(CBTType *treeNode)
{
	if (treeNode)
	{
		return 0;
	}
	else
	{
		return 1;
	}
}

int Tree::TreeDepth(CBTType *treeNode)
{
	int depleft,depright;

	if (treeNode == NULL)
	{
		return 0;
	}
	else
	{
		depleft = TreeDepth(treeNode->left);
		depright = TreeDepth(treeNode->right);
		if (depleft > depright)
		{
			return depleft + 1;
		}
		else
		{
			return depright + 1;
		}
	}
}

void Tree::ClearTree(CBTType *treeNode)
{
	if (treeNode)
	{
		ClearTree(treeNode->left);
		ClearTree(treeNode->right);
		free(treeNode);
		treeNode = NULL;
	}
}

void Tree::TreeNodeData(CBTType *p)
{
	std::cout<<p->data<<" ";
}

void Tree::LevelTree(CBTType *treeNode, void (*TreeNodeData)(CBTType *))
{
	CBTType *p;
	CBTType *q[MANLEN];
	int head = 0,tail = 0;

	if (treeNode)
	{
		tail = (tail + 1) % MANLEN;
		q[tail] = treeNode;
	}
	while (head != tail)
	{
		head = (head + 1) % MANLEN;
		p = q[head];
		TreeNodeData(p);
		if (p->left != NULL)
		{
			tail = (tail + 1) % MANLEN;
			q[tail] = p->left;
		}

		if (p->right != NULL)
		{
			tail = (tail + 1) % MANLEN;
			q[tail] = p->right;
		}

	}
}

void Tree::DLRTree(CBTType *treeNode, void (*TreeNodeData)(CBTType *p))
{
	if (treeNode)
	{
		TreeNodeData(treeNode);
		DLRTree(treeNode->left, TreeNodeData);
		DLRTree(treeNode->right,TreeNodeData);
	}
}

void Tree::LDRTree(CBTType *treeNode, void (*TreeNodeData)(CBTType *))
{
	if (treeNode)
	{
		LDRTree(treeNode->left, TreeNodeData);
		TreeNodeData(treeNode);
		LDRTree(treeNode->right, TreeNodeData);
	}
}

void Tree::LRDTree(CBTType *treeNode, void (*TreeNodeData)(CBTType *))
{
	if (treeNode)
	{
		LRDTree(treeNode->left, TreeNodeData);
		LRDTree(treeNode->right, TreeNodeData);
		TreeNodeData(treeNode);
	}
}

main.cpp

#include <iostream>
using namespace std;
#include "Tree.h"
#include <conio.h>
#include <string.h>

using namespace std;

void TreeNodeData2(CBTType *p)
{
	printf("%c",p->data);
}


int main()
{
	CBTType *root = NULL;
	char menusel;
	Tree *myTree = new Tree();
	void(*TreeNodeData1)(CBTType *p);
	TreeNodeData1 =TreeNodeData2;

	//设置根结点
	root = myTree->InitTree();
	do
	{
		std::cout<<"请输入添加二叉树的结点\n";
		std::cout<<"输入0退出\n";
		std::cout<<"1.添加二叉树的结点\n";

		menusel = getch();
		switch (menusel) {
		case '1':
			myTree->AddTreeNode(root);
			break;

		case '0':
			break;
		default:
			break;
		}

	} while (menusel != '0');

	   //遍历
	    do
	    {
	        std::cout<<"请选择遍历二叉树,输入0退出\n";
	        std::cout<<"1.先序遍历 DLR \t"<<"2.中序遍历 LDR\n";
	        std::cout<<"3.后序遍历 LRD \t"<<"4.按层遍历 LDR\n";
	        scanf("%c",&menusel);
	        switch (menusel) {
	            case '0':
	                break;
	                
	            case '1':
	                std::cout<<"1.先序遍历 DLR的结果:";
	                myTree->DLRTree(root, TreeNodeData1);
	                std::cout<<"\n";
	                break;
	                
	            case '2':
	                std::cout<<"2.中序遍历 LDR的结果:";
	                myTree->LDRTree(root, TreeNodeData1);
	                std::cout<<"\n";
	                break;
	                
	            case '3':
	                std::cout<<"3.后序遍历 LRD的结果:";
	                myTree->LRDTree(root, TreeNodeData1);
	                std::cout<<"\n";     
	                break;
	                
	            case '4':
	                std::cout<<"4.后序遍历 LRD的结果:";
	                myTree->LevelTree(root, TreeNodeData1);
	                std::cout<<"\n";
	                
	                break;
	                
	            default:
	                ;
	        }
	    } while (menusel != '0');

	printf("\n二叉树深度为:%d\n",myTree->TreeDepth(root));
	myTree->ClearTree(root);
	root = NULL;
	delete myTree;

	cout << "helloWorld!";
	system("pause");
	return 0;
}

演示效果图:





参考书籍:《C/C++常用算法手册》  《数据结构-清华大学严蔚敏》

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: C/C++算法常用手册是程序员日常工作中不可或缺的工具书之一。该手册主要收录了程序员在开发过程中常用算法,以及相应的代码实现。该手册涵盖了诸如数据结构、排序、查找、递归、贪心、动态规划、字符串等算法,帮助程序员快速掌握这些算法的基本原理和实现方式。简单地说,该手册将算法的核心原理和实现细节集中在了一起,兼顾了易懂性和实用性。 随着程序员需求的不断增加,该手册逐渐扩充了更多的算法类型。同时,该手册还根据算法的不同应用场景进行分类,方便程序员快速查找和使用。例如,程序员可以通过该手册快速了解不同数据结构的原理和实现方法,了解常见算法的时间复杂度和空间复杂度,还可以查找常见的实际问题中的算法实现方式。 总的来说,C/C++算法常用手册是程序员必备的工具之一,帮助程序员提高算法的实现能力和解决实际问题的能力,提高程序的效率和质量。 ### 回答2: C/C++常用算法手册是一本介绍计算机算法的参考手册,主要面向C/C++语言程序员。该手册总结了各种常用算法,包括排序、查找、图论、字符串等。通过该手册的学习,可以让程序员更好地掌握C/C++编程的技巧和方法。 该手册中介绍了排序算法,包括冒泡排序、插入排序、选择排序、快速排序、归并排序等。对于不同的排序算法,手册详细介绍了它们的思路和实现方法,同时也对它们的时间复杂度和效率进行了分析和比较。 在查找方面,手册介绍了常用的顺序查找和二分查找算法,它们可以帮助程序员快速地定位和查找数据。 在图论和字符串方面,手册介绍了很多有用的算法,如最短路径算法、最小生成算法、字符串匹配算法等。这些算法可以帮助程序员更好地解决实际问题。 总之,C/C++常用算法手册是一本非常实用和有价值的参考书,它可以让程序员掌握更多的C/C++算法技巧,提高程序员的编程能力和开发效率。 ### 回答3: C/C++ 常用算法手册是一本总结了 C/C++ 编程语言中常用算法数据结构、设计模式等知识的参考书籍。 相对于其他语言,C 和 C++ 语言有着更高的执行效率和更多的编程自由度,也因此被广泛应用于开发高性能、底层的软件程序。在这样的应用场景下,对算法数据结构的掌握显得尤为重要。 C/C++ 常用算法手册涵盖了各种基础的算法数据结构,比如排序、查找、链表、等。同时,它也介绍了一些常用的高级算法,比如动态规划、贪心算法和回溯算法。 此外,该手册还详细说明了面向对象编程领域中常用的设计模式和其实现方式,例如工厂模式、装饰器模式等。 阅读 C/C++ 常用算法手册不但能够让读者掌握常用算法的实现方法,更能提高编程思维和技巧。另外,在实际应用中,编写高效的程序不仅需要算法的巧妙运用,更需要细致、严谨的代码风格和设计思路。此时,该手册中丰富的示例代码和编码规范性的讲解也能为读者提供很大的帮助。 总之,C/C++ 常用算法手册是一本既实用又深入的参考书,适合广大 C/C++ 开发者和算法学习者阅读。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热血枫叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值