目录
循环队列
循环队列
分类标准:
1.线性结构:是一个有序数据元素的集合。它应该满足下面的特征:
- 集合中必存在唯一的一个“第一个元素”
-
集合中必存在唯一的一个“最后的元素”
- 除最后一元素之外,其它数据元素均有唯一的“后继
- 除第一个元素之外,其它数据元素均有唯一的“前驱”
我们知道符合条件的数据结构就有栈、队列和其它。
2.非线性结构:其逻辑特征是一个节点元素可以有多个直接前驱或多个直接后继。
那么,符合条件的数据结构就有图、树和其它。
数组
概念
优点:
1、存储多个元素,比较常用
2、访问便捷,使用下标[index]即可访问
缺点:
1、数组的创建通常需要申请一段连续的内存空间,并且大小是固定的(大多数的编程语言数组都是固定的),所以在进行扩容的时候难以掌控。
2、一般情况下,申请一个更大的数组,会是之前数组的倍数,比如两倍。然后,再将原数组中的元素复制过去
3、插入数据越是靠前,其成本很高,因为需要进行大量元素的位移。
栈
概念
栈是一种后进先出(LIFO)线性表,是一种基于数组的数据结构。(ps:其实后面讲到的数据结构或多或少有数组的影子)
说明:
1、LIFO(Last In First Out)表示后进先出,后进来的元素第一个弹出栈空间。类似于自动餐托盘,最后放上去的托盘,往往先被拿出来使用。
2、仅允许在表的一端进行插入和移除元素。这一端被称为栈顶,相对地,把另一端称为栈底。如下图的标识。
3、向一个栈插入新元素称作进栈、入栈或压栈,这是将新元素放在栈顶元素上面,使之成为新的栈顶元素。
4、从一个栈删除元素又称为出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
链表实现:
优点:有链表的部分特性,即元素与元素之间在物理存储上可以不连续
缺点:单向链表在链尾作为栈顶添加删除元素复杂度高
数据结构
链表实现:链式栈、动态栈
数组实现:顺序栈、静态栈
应用
十进制转N进制、行编辑器和校验括号是否匹配(编译器)、中缀表达式转后缀表达式、表达式求值等
无处不在的Undo操作(撤销)
程序调用的系统栈
括号匹配器实现:
import java.util.Stack;
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (int i = 0 ; i < s.length() ; i ++){
char c = s.charAt(i);
if (c == '(' || c == '[' || c == '{'){
stack.push(c);
}else{
if(stack.isEmpty())
return false;
char topChar = stack.pop();
if (c == ')' && topChar != '(')
return false;
if (c == ']' && topChar != '[')
return false;
if (c == '}' && topChar != '{')
return false;
}
}
return stack.isEmpty();
}
public static void main(String[] args) {
System.out.println((new Solution()).isValid("{}[]()")); // true
System.out.println((new Solution()).isValid("([)]")); // false
}
java实现
时间复杂度
动态栈/顺序栈
- void push(e) O(1) 均摊
- E pop() O(1) 均摊
- E peek() O(1)
- int getSize() O(1)
- boolean isEmpty() O(1)
队列
概念
队列是一种先进先出(FIFO)受限的线性表。受限体现在于其允许在表的前端(队头)(front)进行删除操作,在表的末尾(队尾)(rear)进行插入【优先队列这些排除在外】操作。
enQueue(入队):
- 在
队尾
rear插入元素
deQueue(出队):
- 在
对头
front删除元素
普通队列
缺点:每个空间域只能利用一次
。造成空间
极度浪费
。并且非常容易越界,
链表队列
队列是先进先出的,对于链表,我们能采用单链表尽量采用单链表,能方便尽量方便,同时还要兼顾效率
。
-
方案一 如果队头<=>
链表尾
,队尾<=>链表头
。那么队尾进队插入在链表头部插入没问题。容易实现,但是如果队头删除在尾部进行,如果不设置尾指针要遍历到队尾,但是设置尾指针删除需要将它指向前驱节点
那么就需要双向链表。都挺麻烦的。 -
方案二但是如果队头<=>
链表头
,队尾<=>链表尾部
,那么队尾进队插入在链表尾部插入没问题(用尾指针可以直接指向next)。容易实现,如果队头删除在头部进行也很容易,就是我们前面常说的头节点删除节点。 -
所以
最终采取
的是方案2
的带头节点
,带尾指针
的单链表(队头<=>链表头
,队尾<=>链表尾部
)
优点:动态分配内存,不会越界
缺点:每个空间只利用一次
循环队列
它是一个首尾相连的FIFO的数据结构,采用数组的线性空间,数据组织简单。能很快知道队列是否满为空。能以很快速度的来存取数据。
队满状态的判断条件为(rear++)% maxSize == front。队空状态的判断条件为 front == rear
因为有简单高效的原因,甚至在硬件都实现了循环队列.
优点:重复利用分配空间,不会越界
数据结构
数组实现(顺序存储):
普通队列
循环队列
链表实现(链式存储):
链式队列(队头<=>链表头
,队尾<=>链表尾部
)
入队:
出队:
应用
循环队列
循环队列广泛用于网络数据收发,和不同程序间数据交换(比如内核与应用程序大量交换数据,从硬件接收大量数据)均使用了循环队列.
java实现
普通队列
public class seqQueue<T> {
private T data[];// 数组容器
private int front;// 头
private int rear;// 尾
private int maxsize;// 最大长度
public seqQueue(int i)// 设置长为i的int 型队列
{
data = (T[]) new Object[i+1];
front = 0;
rear = 0;
maxsize = i+1;
}
public int lenth() {
return (rear+maxsize-front)%maxsize;
}
public boolean isempty() {
return rear == front;
}
public boolean isfull() {
return (rear + 1) % maxsize == front;
}
public void enQueue(T i) throws Exception// 入队
{
if (isfull())
throw new Exception("已满");
else {
data[rear] = i;
rear=(rear + 1) % maxsize;
}
}
public T deQueue() throws Exception// 出队
{
if (isempty())
throw new Exception("已空");
else {
T va=data[front];
front=(front+1)%maxsize;
return va;
}
}
public String toString()// 输出队
{
String va="队头: ";
int lenth=lenth();
for(int i=0;i<lenth;i++)
{
va+=data[(front+i)%maxsize]+" ";
}
return va;
}
}
循环队列
实现循环队列最重要的一个问题就是队空和队满的判断问题。
队满状态的判断条件为(rear++)% maxSize == front。队空状态的判断条件为 front == rear。
class CircleArray
{
private int front ;
private int rear ;
private int [] arr;
private int maxSize;
public CircleArray(int maxSize) {
this.arr = new int [maxSize] ;
this.maxSize = maxSize;
}
public boolean isEmpty(){
return this.front == this.rear;
}
public boolean isFull(){
return (this.rear+1) % this.maxSize == this.front;
}
public void addQueue(int temp){
if(this.isFull()){
System.out.println("队列满,不能添加数据");
return;
}
this.arr[this.rear] = temp;
this.rear = (this.rear+1) % this.maxSize;
}
public int getQueue(){
if(this.isEmpty()){
throw new RuntimeException("队列空,不能取数据");
}
int value = this.arr[this.front];
this.front = (this.front+1) % this.maxSize;
return value;
}
public int headQueue(){
if(this.isEmpty()){
throw new RuntimeException("队列空,无数据");
}
return this.arr[(this.front+1) % this.maxSize];
}
}
链式队列
public class listQueue<T> {
static class node<T> {
T data;// 节点的结果
node next;// 下一个连接的节点
public node() {}
public node(T data) {
this.data = data;
}
}
node front;//相当于head 带头节点的
node rear;//相当于tail/end
public listQueue() {
front=new node<T>();
rear=front;
}
public int lenth() {
int len=0;
node team=front;
while(team!=rear)
{
len++;team=team.next;
}
return len;
}
public boolean isempty() {
return rear == front;
}
public void enQueue(T value) // 入队.尾部插入
{
node va=new node<T>(value);
rear.next=va;
rear=va;
}
public T deQueue() throws Exception// 出队
{
if (isempty())
throw new Exception("已空");
else {
T va=(T) front.next.data;
front.next=front.next.next;
return va;
}
}
public String toString()
{
node team=front.next;
String va="队头: ";
while(team!=null)
{
va+=team.data+" ";
team=team.next;
}
return va;
}
}
时间复杂度
具体情况具体分析
链表
概念
通过结构体实现动态分配内存,数组的升级。
优点:
真正的动态数据结构,无需关系创建的空间是否过大,不需要像数组一样担心容量的问题。
缺点:
不能像数组那样,给一个索引就能查找到指定的值。查找时间复杂度为O(n)
数据结构
class Node{
E e;//数据存储的地方
Node next;//也是一个节点,他指向当前节点的下一个节点
Node pre;//若为双向链表存在该字段
}
public class LinkdList<E>{
private class Node{
public E e; //data数据
public Node next; //指向下一节点的指针
}
//链表的头部
private Node head;
//节点的长度
private int size;
//有参无参构造方法
public Node(E e, Node next){
this.e = e;
this.next = next;
}
public Node(E e){
this(e, null);
}
public Node(){
this(null, null);
}
}
应用
java实现
见(SUB)java集合类-list_list peek-CSDN博客
时间复杂度
查找:O(n)
字典
概念
key:value对
数据结构
- 使用树
- 使用哈希表
{
'Swaraoop':'Swaroop@byteofpython.nfo',
'Larry':'larry@wall.org',
'Matsumoto':'matz@ruby-lang.org',
'Spammer':'spammer@hotmail.com'
}
应用
JSON,数据去重
java实现
见(SUB)java集合类-map_weixin_38681369的博客-CSDN博客
时间复杂度
具体实现具体分析
集合
概念
数据结构
应用
java实现
map:(SUB)java集合类-map_weixin_38681369的博客-CSDN博客
list:(SUB)java集合类-list_list peek-CSDN博客
时间复杂度
散列表/哈希表
概念
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
优缺点
优点:不论哈希表中有多少数据,查找、插入、删除(有时包括删除)只需要接近常量的时间即0(1)的时间级。实际上,这只需要几条机器指令。
哈希表运算得非常快,在计算机程序中,如果需要在一秒种内查找上千条记录通常使用哈希表(例如拼写检查器)哈希表的速度明显比树快,树的操作通常需要O(N)的时间级。哈希表不仅速度快,编程实现也相对容易。
如果不需要有序遍历数据,并且可以提前预测数据量的大小。那么哈希表在速度和易用性方面是无与伦比的。
缺点:它是基于数组的,数组创建后难于扩展,某些哈希表被基本填满时,性能下降得非常严重,所以程序员必须要清楚表中将要存储多少数据(或者准备好定期地把数据转移到更大的哈希表中,这是个费时的过程)。
数据结构
开地址法:
数组[字典]
拉链法:
链式散列表:数组[链表<字典/node>]
应用
hash应用:
1.信息安全领域中加密算法
2.查找:哈希表,又称为散列,是一种更加快捷的查找技术。我们之前的查找,都是这样一种思路:集合中拿出来一个元素,看看是否与我们要找的相等,如果不等,缩小范围,继续查找。而哈希表是完全另外一种思路:当我知道key值以后,我就可以直接计算出这个元素在集合中的位置,根本不需要一次又一次的查找!
3.Hash表在海量数据处理中有着广泛应用。
java实现
具体代码实现详见java的hashmap/hashset。
哈希函数设计原则:
.哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
.哈希函数计算出来的地址能均匀分布在整个空间中
.哈希函数应该比较简单
常见hash函数
1.直接定址法
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
优点:简单、均匀
缺点:需要事先知道关键字的分布情况
适合查找比较小且连续的情况
2.除留余数法
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址3.平方取中法
假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址;
再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况
4.折叠法
折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况
5.随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数函数通常应用于关键字长度不等时采用此法
6.数学分析法
设有n个d位数,每一位可能有r种不同的符号,这r种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。
7.平方取中法
- 先计算出关键字值的平方,然后取平方值中间几位作为散列地址。
- 随机分布的关键字,得到的散列地址也是随机分布的。
如何解决hash冲突
开地址法
- 线性探测法:d=1,2,3,…,m-1
- 二次探测法:d=12,-12,22,-22,32,…
- 伪随机数探测法:d=伪随机数
eg:现有第 4 个数据 38 ,当通过哈希函数求得的哈希地址为 5,与 60 冲突,则分别采用以上 3 种方式求得插入位置如图:
再哈希法
当通过哈希函数求得的哈希地址同其他关键字产生冲突时,使用另一个哈希函数计算,直到冲突不再发生。
链地址法(拉链法)
将所有产生冲突的关键字所对应的数据全部存储在同一个线性链表中。当冲突太多的时候,我们一般采用的方法时拉链法(也有可能hash函数有问题)
建立公共溢出区
基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表.(注意:在这个方法里面是把元素分开两个表来存储)。当数据表太小数据太多可以通过建立一个溢出表,专门用来存放溢出记录。
时间复杂度
增删改查:O(1)
树
概念
数据结构
应用
java实现
树的遍历:(SUB)数据结构-树_stack<treenode> stack = new stack();解释一下_weixin_38681369的博客-CSDN博客