更多详细内容,请微信搜索“前端爱好者
“, 戳我 查看 。
数据结构
数据结构是计算机科学的基础概念之一,它涉及到如何组织和存储数据,以便能够有效地进行访问、管理和操作。数据结构是一种描述或表示数据的方式,它定义了数据的存储方式、关系以及数据操作的方法。
以下是一些常见的数据结构类型:
- 树(Tree) :这是一种非线性的数据结构,用于表示具有层次关系的数据。树的一个常见例子是二叉树,其中每个节点最多有两个子节点。
- 图 (Graph) :这是一种非线性的数据结构,用于表示任意数量的节点以及它们之间的连接关系。图可以是无向的或有向的,可以包含环,可以自我连接。
- 堆 (Heap) :这是一种数据结构,用于存储不重复的元素。集合的主要操作有添加元素(add)和删除元素(remove)。
- 哈希表/散列表 (Hash) :这是一种数据结构,用于实现快速查找操作。哈希表通过哈希函数将键映射到桶中,然后在桶中进行查找和存储。
- 栈 (Stack) :这是一种后进先出(LIFO)的数据结构,只能从一端进行插入和删除操作。栈的主要操作有push(添加元素)和pop(删除元素)。
- 队列 (Queue) :这是一种先进先出(FIFO)的数据结构,可以从一端添加元素,从另一端删除元素。队列的主要操作有enqueue(添加元素)和
- 数组 (Array) :这是一种线性数据结构,可以看作是一系列具有相同类型的元素集合。数组在内存中是连续的,可以通过索引访问任何位置的元素。
- 链表 (Likend List) :这是一种线性数据结构,由一系列节点组成,每个节点包含一个值和一个指向下一个节点的指针。链表不要求所有元素都连续在内存中。
这些常见的数据结构,它们各有优缺点,应根据具体的应用场景选择合适的数据结构。
例如,对于大量元素的存储和访问,数组和哈希表可能是较好的选择;对于需要保持元素插入和删除操作高效的数据,链表和动态数组可能是较好的选择;对于需要保持元素搜索操作高效的数据,搜索树可能是较好的选择。
此外,数据结构和算法通常是紧密相关的。
理解并掌握各种数据结构及其操作方法,可以帮助我们更有效地设计和实现算法,并优化程序的性能。
树(Tree)
树的定义
树是n(n>=0)个结点的有限集。当n = 0时,称为空树
。
在任意一棵非空树中应满足:
- 有且仅有一个特定的称为根的结点。
- 当n>1时,其余节点可分为m(m>0)个互不相交的有限集T1,T2,…,Tm,其中每个集合本身又是一棵树,并且称为根的子树。
显然,树的定义是递归的,即在树的定义中又用到了自身,树是一种递归的数据结构。
树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点:
- 树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
- 树中所有结点可以有零个或多个后继。
因此n个结点的树中有n-1条边。
基本术语
下面结合图示来说明一下树的一些基本术语和概念。
结点、祖先、子孙、双亲、兄弟
考虑 结点 K。根A到结点K的唯一路径上的任意结点,称为结点K的 祖先。如结点B是结点K的祖先,而结点K是结点B的 子孙。路径上最接近结点K的结点E称为K的 双亲,而K为结点E的孩子。根A是树中唯一没有双亲的结点。有相同双亲的结点称为 兄弟,如结点K和结点L有相同的双亲E,即K和L为兄弟。
结点的度、树的度
树中一个结点的孩子个数称为该 结点的度,树中结点的最大度数称为 树的度。
如结点B的度为2,结点D的度为3,树的度为3。
分支结点/非终端结点、叶子结点/终端结点
- 度大于0的结点称为 分支结点(又称非终端结点);
- 度为0(没有子女结点)的结点称为 叶子结点(又称 终端结点)。
在分支结点中,每个结点的分支数就是该结点的度。
结点的深度、高度和层次
- 结点的层次从树根开始定义,根结点为第1层,它的子结点为第2层,以此类推。双亲在同一层的结点互为 堂兄弟,图中结点G与E,F,H,I,J互为堂兄弟。
- 结点的深度是从根结点开始自顶向下逐层累加的。
- 结点的高度是从叶结点开始自底向上逐层累加的。
- 树 **的高度(或深度)**是树中结点的最大层数。图中树的高度为4。
有序树和无序树。
- 树中结点的各子树从左到右是
有次序
的,不能互换
,称该树为有序树
,否则称为无序树
。
假设图为有序树,若将子结点位置互换,则变成一棵不同的树
。
路径和路径长度。
树中两个结点之间的 路径 是由这两个结点之间所经过的结点序列构成的,而 路径长度 是路径上所经过的边的个数。
注意:由于树中的分支是有向的,即从双亲指向孩子,所以 树中的路径是从上向下的,同一双亲的两个孩子之间不存在路径。
森林。
森林是m (m≥0)棵 互不相交
的树的集合
。
森林的概念与树的概念十分相近,因为只要把树的根结点删去就成了森林。
反之,只要给m棵独立的树加上一个结点,并把这m棵树作为该结点的子树,则森林就变成了树。
树的性质
树具有如下最基本的性质:
树的存储结构
在介绍以下三种存储结构的过程中,我们都以下面这个树为例子。
双亲表示法
我们假设以一组连续空间存储树的结点,同时在每个结点中,附设一个指示器指示其双亲
结点到链表中的位置。也就是说,每个结点除了知道自已是谁以外,还知道它的双亲在哪里。
其中data是数据域,存储结点的数据信息。而parent是指针域,存储该结点的双亲在数组中的下标。
以下是我们的双亲表示法的结点结构定义代码。
/*树的双亲表示法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int TElemType; //树结点的数据类型,目前暂定为整型
/*结点结构*/
typedef struct PTNode{
TElemType data; //结点数据
int parent; //双亲位置
}PTNode;
/*树结构*/
typedef struct{
PTNode nodes[MAX_TREE_SIZE]; //结点数组
int r, n; //根的位置和结点数
}PTree;
这样的存储结构,我们可以根据结点的parent 指针很容易找到它的双亲结点,所用的时间复杂度为0(1),直到parent为-1时,表示找到了树结点的根。
可如果我们要知道结点的孩子是什么,对不起,请遍历整个结构
才行。
孩子表示法
具体办法是:把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针又组成-一个线性表,采用顺序存储结构,存放进一个一维数组中,如图所示。
为此,设计两种结点结构:
一个是孩子链表的孩子结点。
其中:
child是数据域,用来存储某个结点在表头数组中的下标。
next 是指针域,用来存储指向某结点的下一个孩子结点的指针。
另一个是表头数组的表头结点。
其中:
data是数据域,存储某结点的数据信息。
firstchild 是头指针域,存储该结点的孩子链表的头指针。
以下是我们的孩子表示法的结构定义代码。
/*树的孩子表示法结构定义*/
#define MAX_TREE_SIZE 100
/*孩子结点*/
typedef struct CTNode{
int child;
struct CTNode *next;
}*ChildPtr;
/*表头结点*/
typedef struct{
TElemType data;
ChildPtr firstchild;
}CTBox;
/*树结构*/
typedef struct{
CTBox nodes[MAX_TREE_SIZE]; //结点数组
int r, n; //根的位置和结点数
}
这样的结构对于我们要查找某个结点的某个孩子,或者找某个结点的兄弟,只需要查找这个结点的孩子单链表即可。对于遍历整棵树也是很方便的,对头结点的数组循环即可。
但是,这也存在着问题,如何知道某个结点的双亲是谁呢? 比较麻烦,需要整棵树遍历才行,难道就不可以把双亲表示法和孩子表示法综合一下吗?当然是可以,这个读者可自己尝试结合一下,在次不做赘述。
孩子兄弟表示法
刚才我们分别从双亲的角度和从孩子的角度研究树的存储结构,如果我们从树结点的兄弟的角度又会如何呢?
当然,对于树这样的层级结构来说,只研究结点的兄弟是不行的,我们观察后发现,任意一棵树, 它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的
。 因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
结点的结构如下:
其中:
- data是数据域,
- firstchild 为指针域,存储该结点的第一个孩子结点的存储地址,
- rightsib 是指针域,存储该结点的右兄弟结点的存储地址。
这种表示法,给查找某个结点的某个孩子带来了方便。
结构定义代码如下。
/*树的孩子兄弟表示法结构定义*/
typedef struct CSNode{
TElemtype data;
struct CSNode *firstchild, *rightsib;
} CSNode, *CSTree;
于是通过这种结构,我们就把原来的树变成了这个样子:
这不就是个二叉树么?
没错,其实这个表示法的最大好处就是它把一棵复杂的树变成了一棵二叉树。
参考文档
- https://blog.csdn.net/Bb15070047748/article/details/119208588
- https://blog.csdn.net/Real_Fool_/article/details/113930623