一、线性数据结构
1.数组
2.链表
3.栈
(1)简介
- 栈(Stack)只允许在有序的线性数据集合的一端(栈顶top)进行加入数据(push)和移除数据(pop)。按照后进先出(LIFO,Last In First Out)的原理运作。
- 栈常用一维数组或链表来实现,用数组实现的栈叫做顺序栈,用链表实现的栈叫做链式栈。
- 在栈中,push和pop的操作都是发生在栈顶。
假设堆栈中有n个元素。
访问:O(n) //最坏情况
插入删除:O(1) //顶端插入和删除元素
4.队列
(1)简介
- 队列(Queue)是先入先出(FIFO,First In First Out)的线性表。
- 在具体应用中通常用链表或者数组来实现,用数组实现的队列叫做顺序队列,用链表实现的队列叫做链式队列。
- 队列只允许在后端(rear)进行插入操作,也就是入队enqueue;在前端(front)进行删除操作,也就是出队dequeue
假设队列中有n个元素。
访问:O(n) //最坏情况
插入删除:O(1) //后端插入,前端删除
(2)队列分类
1)单队列
- 单队列就是常见的队列,每次添加元素时,都是添加到队尾。
- 单队列又分为顺序队列(数组实现)和链式队列(链表实现)。
- 存在的问题:假溢出、越界。
- 顺序队列存在“假溢出”的问题,也就是明明有位置却不能添加的情况。
假设下图时一个顺序队列,我们将前两个元素1,2出队,并入队两个元素7,8。当进行入队、出队操作的时候,front和rear都会持续往后移动,当rear移动到最后的时候,我们无法再往队列中添加数据,即使数组中还有空余空间,这种现象就是“假溢出”。 - 除了假溢出问题之外,当添加元素8时,rear指针移动到数组之外(越界)。
为了避免当只有一个元素的时候,队头和队尾重合使处理变得麻烦,所以引入两个指针,front 指针指向对头元素,rear 指针指向队列最后一个元素的下一个位置,这样当 front 等于 rear 时,此队列不是还剩一个元素,而是空队列。——From 《大话数据结构》
- 顺序队列存在“假溢出”的问题,也就是明明有位置却不能添加的情况。
2)循环队列
3)双端队列
4)优先队列
(3)队列的常见应用场景
二、图
三、堆
四、树
1.树的特点及常用概念
(1)特点
任何一颗非空树只有一个根节点。
一棵树具有以下特点:
- 一棵树中的任意两个结点有且仅有唯一的一条路径连通。
- 一棵树如果有n个结点,那么它一定恰好有n-1条边。
- 一棵树不包含回路。
(2)常用概念
以二叉树为例
- 节点(结点):树中的每个元素都可以统称为节点。
- 根节点:顶层节点或者说没有父节点的节点。A节点-根节点
- 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。B节点-D节点和E节点的父节点
- 子节点:一个节点含有的子树的根节点称为该节点的子节点。D节点、E节点-B节点的子节点
- 兄弟节点:具有相同父节点的节点互称为兄弟节点。D节点和E节点的父节点都是B节点,故D和E为兄弟节点
- 叶子节点:没有子节点的节点。D、H、F、I都是叶子节点
- 节点的高度:该节点到叶子节点的最长路径所包含的边数。
- 节点的深度:根节点到该节点的路径所包含的边数,根节点的深度为0。
- 节点的层数:节点的深度+1。
- 树的高度:根节点的高度。
2.二叉树的分类
- 二叉树(Binary tree)是每个节点最多只有两个分支(即不存在分支度大于2的节点)的树结构。
- 二叉树的分支通常被称作“左子树”或“右子树”。并且,二叉树的分支具有左右次序,不能随意颠倒。
- 二叉树的第i层至多拥有2^(i-1)个节点,深度为k(深度分别为0,1,2,...,k;层数为k+1)的二叉树至多总共有2^(k+1)-1个节点(满二叉树的情况),至少有2^k个节点(完全二叉树,最后一层只有一个节点,其余层是满的的情况)。
(1)满二叉树
一个二叉树,如果每一层的节点数都达到了最大值,则这个二叉树就是满二叉树。
如果一个满二叉树的层数为K,那么它的节点总数为2^k-1。
(2)完全二叉树
除最后一层外,若其余层都是满的,并且最后一层或是满的,或是在右边缺少连续若干节点,则这个二叉树就是完全二叉树。
(完全二叉树的最后一层最多是满的,最少只有一个节点,其余层都是满的)
大家可以想象为一棵树从根节点开始扩展,扩展完左子节点才能扩展右子节点,每扩展完一层,才能继续扩展下一层。
完全二叉树的性质:父节点和子节点的序号有着对应关系。
(3)平衡二叉树
平衡二叉树是一颗二叉排序树,且具有以下性质:
- 可以是一颗空树。
- 如果不是空树,它的左右两个子树的高度差的绝对值不超过1(0或者1),并且左右两个子树都是一颗平衡二叉树。
平衡二叉树的常用实现方法有:红黑树、AVL树、替罪羊树、加权平衡树、伸展树等。
(4)补充
这棵树已经退化为一个链表,我们称之为斜树。
二叉树相比于链表,由于父子节点以及兄弟节点之间往往具有某种特殊的关系,使得我们在树中对数据进行搜索和修改时,相对于链表更加快捷便利。但如果二叉树退化为一个链表,那么作为树所具有的优秀品质就难以表现出来,效率也会大打折扣。为了避免这种情况,我们希望每个做“家长”(父节点)的都一碗水端平,分给左儿子和右儿子的尽可能一样多,相差最多不超过一层,即平衡二叉树。
3.二叉树的存储
二叉树的存储主要分为链式存储和顺序存储两种。
(1)链式存储
和链表类似,二叉树的链式存储依靠指针将各个节点串联起来,不需要连续的存储空间。
每个节点包含三个属性:
- data:数据。data不一定是单一的数据,根据不同情况,可以是多个具有不同类型的数据。
- left:指向左节点。
- right:指向右节点。
(2)顺序存储
顺序存储就是利用数组进行存储,数组中的每一个位置仅存储节点的data,不存储left和right,子节点的索引通过数组下标完成。
完全二叉树的顺序存储:
根节点的序号为1,对于每个节点Node,假设它存储在数组中下标为i的位置,那么它的左子节点就存储在2i的位置,右子节点存储在2i+1的位置。
非完全二叉树的顺序存储:
如果我们要存储的二叉树不是完全二叉树,在数组中就会出现空隙,导致内存利用率降低。
4.二叉树的遍历
(1)先序遍历
二叉树的先序遍历,就是先输出根节点,再遍历左子树,最后遍历右子树,遍历左子树和右子树的时候,同样遵循先序遍历的规则,也就是说,我们可以递归实现先序遍历。
public void preOrder(TreeNode root){
if(root == null){
return;
}
system.out.println(root.data);
preOrder(root.left);
preOrder(root.right);
}
(2)中序遍历
二叉树的中序遍历,就是先递归中序遍历左子树,再输出根节点的值,再递归中序遍历右子树。
大家可以想象成一巴掌把树压扁,父节点被拍到了左子节点和右子节点的中间。
public void inOrder(TreeNode root){
if(root == null){
return;
}
inOrder(root.left);
system.out.println(root.data);
inOrder(root.right);
}
(3)后序遍历
二叉树的后序遍历,就是先递归后序遍历左子树,再递归后序遍历右子树,最后输出根节点。
public void postOrder(TreeNode root){
if(root == null){
return;
}
postOrder(root.left);
postOrder(root.right);
system.out.println(root.data);
}