线性数据结构:数组、链表、栈、队列
非线性结构:集合、树形结构、图状结构
数据结构之线性结构和非线性结构_HOLD ON!的博客-CSDN博客_数据结构的线性结构和非线性结构
一、栈
1、检查符号是否成对出现
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断该字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
比如 "()"、"()[]{}"、"{[]}" 都是有效字符串,而 "(]" 、"([)]" 则不是。
代码:
//检查符号是否成对出现
/*只要遇见右括号就比较栈顶是否是他的对应括号:之前匹配的也都全出栈了*/
private boolean isValid(String s) {
HashMap<Character,Character> map=new HashMap<>();
map.put(')','(');
map.put('}','{');
map.put(']','[');
Stack<Character> stack=new Stack<>();
char[] chars=s.toCharArray();
for (char ch:chars){
if (map.containsKey(ch)){
char element=stack.pop();//配对就出栈啦,不配对就返回false
if (element!=map.get(ch)){
return false;
}
}else {
stack.push(ch);
}
}
return stack.empty();
}
二、队列
1、单队列
2、循环队列
队列初始:rear=0,font=0,font指向第一个元素,rear指向最后一个元素的下一个
队列空:rear=font
队列满:(rear+1)%maxSize==font
队列元素个数:(rear-font+maxSize)%maxSize
遍历队列:
for (int i=font;i<font+((rear-font+maxSize)%maxSize);i++){
System.out.printf("arr[%d]=%d\n",i%maxSize,arr[i%maxSize]);
}
3、应用场景 *
- 阻塞队列: 阻塞队列可以看成在队列基础上加了阻塞操作的队列。当队列为空的时候,出队操作阻塞,当队列满的时候,入队操作阻塞。使用阻塞队列我们可以很容易实现“生产者 - 消费者“模型。
- 线程池中的请求/任务队列: 线程池中没有空闲线程时,新的任务请求线程资源时,线程池该如何处理呢?答案是将这些请求放在队列中,当有空闲线程的时候,会循环中反复从队列中获取任务来执行。队列分为无界队列(基于链表)和有界队列(基于数组)。无界队列的特点就是可以一直入列,除非系统资源耗尽,比如 :FixedThreadPool 使用无界队列 LinkedBlockingQueue。但是有界队列就不一样了,当队列满的话后面再有任务/请求就会拒绝,在 Java 中的体现就是会抛出java.util.concurrent.RejectedExecutionException 异常。
- Linux 内核进程队列(按优先级排队)
- 现实生活中的派对,播放器上的播放列表;
- 消息队列
- 等等......
三、单链表
链表的插入和删除操作的复杂度为 O(1) ,只需要知道目标位置元素的上一个元素即可。但是,在查找一个节点或者访问特定位置的节点的时候复杂度为 O(n) 。
常见链表分类:
- 单链表
- 双向链表
- 循环链表
- 双向循环链表
数组 vs 链表
- 数组支持随机访问,而链表不支持。
- 数组使用的是连续内存空间对 CPU 的缓存机制友好,链表则相反。
- 数组的大小固定,而链表则天然支持动态扩容。如果声明的数组过小,需要另外申请一个更大的内存空间存放数组元素,然后将原数组拷贝进去,这个操作是比较耗时的!
四、树
1、树的概念
树就是一种类似现实生活中的树的数据结构。任何一颗非空树只有一个根节点
一棵树具有以下特点:
- 一棵树的任意两个结点有且仅有唯一的一条路径联通
- 一棵树如果有n个结点,那么它一定恰好有n-1条边
- 一棵树不包含回路
2、二叉树
什么是二叉树?
二叉树是树的一种类型,每个节点最多只有两个分支的树结构。二叉树的分支通常被称作左子树或右子树。二叉树的分支具有左右次序,不能随意颠倒。
二叉树相关计算:
- 二叉树的第i层至多拥有2(i-1) 个节点
- 深度为k的二叉树至多总共有2(k+1)-1个节点,至少有2k个节点
- n0=n2+1
- 具有n个节点的完全二叉树深度为|log2n|+1
3、满二叉树
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是 满二叉树。也就是说,如果一个二叉树的层数为 K,且结点总数是(2^k) -1 ,则它就是 满二叉树
4、完全二叉树
除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则这个二叉树就是 完全二叉树 。
完全二叉树的特性:
当根节点的值为 1 的情况下,若父结点的序号是 i,那么左子节点的序号就是 2i,右子节点的序号是 2i+1。
5、平衡二叉树
斜树
改进-平衡二叉树
平衡二叉树是一颗二叉排序数,具有以下性质:
- 可以是一课空树
- 如果不是空树,它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一个平衡二叉树。
平衡二叉树的常用实现方法有红黑树、AVL树、替罪羊树、加权平衡树、伸展树等
6、二叉树的存储
二叉树的存储主要分为链式存储和顺序存储两种:
链式存储
和链表类似,二叉树的链式存储依靠指针将各个节点串联起来,不需要连续的存储空间。
每个节点包括三个属性:
- 数据data。data不一定是单一的数据,根据不同情况,可以是多个具有不同类型的数据
- 左节点指针left
- 右节点指针right
Java中用引用对象
指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元,即指针是一个实体;而引用跟原来的变量实质上是一个东西,只不过是原变量的一个别名而已
顺序存储
顺序存储就是利用数组进行存储,数组中的每一个位置仅存储节点的data,不存储左右子节点的指针,子节点的索引通过数组下标完成。根节点的序号为1,对于每个节点Node,假设它存储在数组中下标为i的位置,那么它的左子节点就存储在2i的位置,右子节点存储在下标在2i+1的位置
一课完全二叉树的数组顺序存储如下图;
如果不是完全二叉树,存储会出现数组空隙,导致内存利用率低
7、二叉树的遍历
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;
}
preOrder(root.left);
System.out.println(root.data);
preOrder(root.right);
}
3)后序遍历
顺序:
- 左子树
- 右子树
- 根节点
代码:
public void postOrder(TreeNode root){
if (root==null){
return;
}
preOrder(root.left);
preOrder(root.right);
System.out.println(root.data);
}