文章目录
思维导图
学习路线
常见线性结构
- 线性结构(Linear List)是由n(n>=0)个数据元素(结点)a[0],a[1],a[2]…,a[n-1]组成的有序序列。
- 其中:
数据元素的个数n定义为表的长度 = list.length()
list.length=0 表示没有一个元素时称为空表
将非空的线性表(a>=1)记作:(a[0],a[1],a[2]…,a[n-1])
数据元素a[i] (0<= i <= n-1)只是个抽象符号,其具体含义在不同情况下可以不同
数组(Array)结构
数组(Array)结构是一种重要的数据结构:
- 几乎每种编程语言都会提供一种
原生数据结构
(自带的); - 并且我们
可以借住数据结构来实现其他的数据结构
,比如栈(Stack)、队列(Queue)、堆(Heap);
通常数组的内存是连续的,所以数组在知道下标值的情况下,访问小绿是非常高的
栈结构(Stack)
认识栈结构和特性
数组是一种线性结构,并且可以在数组的任意位置插入和删除数据。
但是有时候我们为了实现某些功能,必须对这种任意性加以限制。
而栈和队列就是比较常见的受限的线性结构。
栈结构示意图
栈它是一种受限的线性结构,先进后出(LIFO)
- 其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对的,把另一端称为栈底。
- LIFO(last in first out)表示就是后进入的元素,第一个弹出栈空间。
- 像一个栈插入新元素又称为进栈、入栈或压栈。它是把新的元素放到栈顶元素的上门,使之成为新的栈顶元素。
- 从一个栈删除元素又称作出栈或者退栈,它是把栈顶元素删除,使其相邻的元素成为新的栈顶元素。
面试题目
栈结构实现
实现栈结构:
- 基于数组实现
- 基于链表实现
创建栈结构的类
创建一个栈的类
// 封装Stack
class ArrayStack {
// 定义一个数组,用于存储元素
private data:any[] = []
// 实现栈操作方法
push() {
}
}
代码解析:
- 我们创建了一个Stack,用户创建栈的类,可以定义一个泛型类。
- 在构造函数中,定义了一个变量,这个变量可以用于保存当前栈对象中所有的元素。
- 这个变量是一个数组类型。
- 我们之后无论是压栈操作还是出栈操作,都是从数组中添加和删除元素。
- 栈有一些相关的操作方法,通常无论是什么语言,操作都是比较类似的。
栈常见有哪些操作呢?
- push(element):添加一个新元素到栈顶位置。
- pop()∶移除栈顶的元素,同时返回被移除的元素。
- peek():返回栈顶的元素,不对栈做任何修改(这个方法不会移除栈顶的元素,仅仅返回它)。
- isEmpty():如果栈里没有任何元素就返回true,否则返回false。
- size():返回栈里的元素个数。这个方法和数组的length属性很类似。
源码
// 公共结构
interface IList<T> {
// peek查看第一个
peek(): T | undefined;
// 判断非空
isEmpty(): boolean;
// 返回队列长度
get size(): number;
}
// 定义栈结构
interface BasisStack<T> extends IList<T> {
push(element:T):void
pop():T | undefined
}
// 封装Stack
class ArrayStack<T = any> implements BasisStack<T> {
// 定义一个数组,用于存储元素
private data: T[] = [];
// 实现栈操作方法
// 元素压入栈
push(element: T): void {
this.data.push(element);
}
// 将栈顶元素弹出栈
pop(): T | undefined {
return this.data.pop();
}
// 查看栈顶元素
peek(): T | undefined {
return this.data[this.data.length - 1];
}
// 判断栈是否为空
isEmpty(): boolean {
return this.data.length === 0;
}
// 返回栈的数据个数
get size(): number {
return this.data.length;
}
}
export default ArrayStack;
队列结构(Queue)
认识队列以及特性
受限的线性结构:
- 我们已经学习了一种受限的线性结构:栈结构
- 这种受限结构对于解决某些特定问题,有特别效果
- 我们接下来学习另一个受限结构:队列
队列(Queue)它是一种受限的线性表,先进先出(FIFO First In First Out)
- 在于它只允许在队列的前端(front)进行删除操作
- 而在队列的后端(rear)进行插入操作
线程队列:
- 在开发中,为了让任务可以并行处理,通常会开启多线程;
- 但是我们不能让大量的线程同时处理任务(占用资源);
- 如果需要开启线程处理任务,我们就会使用线程队列;
- 线程队列会依照次序来启动线程,并且处理对应的任务;
队列还有很多其他应用,后续很多算法中也会用到队列(比如二叉树的层序遍历)
实现队列结构封装
队列的实现和栈一样,有两种方案:
- 基于数组实现
- 基于链表实现
创建一个类实现队列
队列结构常见方法
常见操作:
- enqueue(element):向尾部添加一个(或多个)新的项。
- dequeue():移除队列的第一(即排在队列最前面的)项,并返回被移除的元素。
- front/peek():返回一队列中的第一个元素----最先被添加的,也就是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息—与Stack类的peek方法非常相似)。
- isEmpty():如果队列中不包含任何元素,返回true,否则返回false。
- size():返回队列包含的元素个数,与数组的length属性类似。
源码
interface IList<T> {
// peek查看第一个
peek(): T | undefined;
// 判断非空
isEmpty(): boolean;
// 返回队列长度
get size(): number;
}
interface BasisQueue<T> extends IList<T> {
// 入队方法
enqueue(element: T): void;
// 出队方法
dequeue(): T | undefined;
}
class ArrayQueue<T> implements BasisQueue<T> {
// 内部通过数组保存
private data: T[] = [];
enqueue(element: T): void {
this.data.push(element);
}
dequeue(): T | undefined {
return this.data.shift();
}
peek(): T | undefined {
return this.data[0];
}
isEmpty(): boolean {
return this.data.length === 0;
}
get size(): number {
return this.data.length;
}
}
export default ArrayQueue;
面试题
击鼓传花
使用队列可以非常方便的实现最终结果
原游戏规则:
- 所有人围成一个圈,从末尾同学手里开始向旁边的同学传花。
- 这个时候当鼓声停止,花落谁家,谁就中大奖。
修改后的规则:
- 所有人一圈开始数数,数到某个数字时自动淘汰
- 最后剩下的这个人会获取胜利,请问最后剩下的时原来在哪一个位置上的人?
封装一个基于队列的函数:
- 参数:所有人参与的姓名,基于数字
- 结果:最终剩下一个人的名字
约瑟夫环:
- 阿桥问题(有时也称为约瑟夫斯置换),是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。
- 人们站在一个等待被处决的圈子里。
- 计数从圆圈中的指定点开始,并沿指定方向围绕圆圈进行。在跳过指定数量的人之后,处刑下一个人。
- 对剩下的人重复该过程,从下一个人开始,朝同一方向跳过相同数量的人,直到只剩下一个人,并被释放。
- 在给定数量的情况下,站在第几个位置可以避免被处决?