• 周六. 10 月 12th, 2024

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

【面试篇】数据结构-线性表

King Wang

1 月 3, 2022

数据结构-线性表

  • 线性表是具有n个相同类型元素的有限序列(n>=0)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFvIztzX-1605069443796)(D:\software\typora\workplace\imgs_data_structure_arraylist\1.png)]

常见的线性表有:

  • 数组ArrayList
  • 链表LinkedList
  • 栈Stack
  • 队列Queue
  • 哈希表(散列表)(单独摘出来)

一、 动态数组ArrayList

  • 数组是一种顺序存储的线性表,所有元素的内存地址是连续的。
    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V8qXlXwr-1605069443798)(D:\software\typora\workplace\imgs_data_structure_arraylist\2.png)]

  • 但在很多编程语言中,数组都有个致命的缺陷,无法动态修改容量

  • 实际开发中,我们希望数组的容量是可以动态改变的。

1.动态数组接口设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RgmZihdz-1605069443799)(D:\software\typora\workplace\imgs_data_structure_arraylist\3.png)]

2.动态数组的设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-34M9gasb-1605069443801)(D:\software\typora\workplace\imgs_data_structure_arraylist\4.png)]

// 成员变量
int size;
int[] elements;
// 常量
private static final int DEFAULT_CAPATICY = 10;
// 有参构造函数
public ArrayList(int capaticy) {

elements = new int[capaticy];
}
// 无参构造函数
public ArrayList() {

this(DEFAULT_CAPATICY);
}

3.重要接口实现

3.1 添加元素-add(E element)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b21LXMJ2-1605069443803)(D:\software\typora\workplace\imgs_data_structure_arraylist\5.png)]

/**
* 添加元素到最后面
* @param element
*/
public void add(int element) {

add(size,element);
}

3.2 打印数组

  • 重写toString方法
  • 在toString方法中将元素拼接成字符串
  • 字符串拼接建议使用StringBuilder
/**
* 重写toString方法
*/
@Override
public String toString() {

// TODO Auto-generated method stub
StringBuilder res = new StringBuilder();
res.append("size=").append(size).append(", [");
for(int i=0;i<size;i++) {

if(i!=0) {

res.append(", ");
}
res.append(elements[i]);
}
res.append("]");
return res.toString();
}
}

3.3 删除元素-remove(int index)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k98Zdxnk-1605069443805)(D:\software\typora\workplace\imgs_data_structure_arraylist\6.png)]

 /**
* 删除index位置对应的元素
* @param index
* @return
*/
public int remove(int index) {

// 对index进行判断
rangeCheck(index);
int oldElement = elements[index];
for(int i=index+1;i<size;i++) {

elements[i-1] = elements[i];
}
size --;
return oldElement;
}

3.4 添加元素-add(int index,int element)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R3QCA1Zy-1605069443806)(D:\software\typora\workplace\imgs_data_structure_arraylist\7.png)]

/**
* 往index位置添加元素
* @param index
* @param element
*/
public void add(int index,int element) {

// index判断可以往size位置添加
rangeForCheck(index);
// 对其添加元素
for(int i=size;i>index;i--) {

elements[i] = elements[i-1];
}
elements[index] = element;
size++;
}

3.5 扩容

  • 在add前进行检查当前数组的size与数组申请的大小比较

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OyACb5Mp-1605069443807)(D:\software\typora\workplace\imgs_data_structure_arraylist\8.png)]

/**
* 往index位置添加元素
* @param index
* @param element
*/
public void add(int index,int element) {

// index判断可以往size位置添加
rangeForCheck(index);
// 对其进行判断是否需要扩容
checkForCapaticy(size+1);
// 对其添加元素
for(int i=size;i>index;i--) {

elements[i] = elements[i-1];
}
elements[index] = element;
size++;
}
/**
* 检查容量并扩容
*/
private void checkForCapaticy(int capaticy) {

int oldCapaticy = elements.length;
if(oldCapaticy >= capaticy) {

return;
}
// 扩容
// 新容量为旧容量的1.5倍
int newCapaticy = oldCapaticy + oldCapaticy >> 1;
int newElements[] = new int[newCapaticy];
for(int i=0;i<size;i++) {

newElements[i] = elements[i];
}
elements = newElements;
}

3.6 缩容

  • 如果内存使用比较紧张,动态数组有比较多的剩余空间,可以考虑进行缩容操作
  • 比如剩余空间占总容量的一半时,就进行缩容
  • 注意:如果扩容倍数、缩容时机设计不得当,有可能会导致复杂度震荡。

3.7 泛型

  • 使用泛型技术可以让动态数组更加通用,可以存放任何数据类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lEyshB7f-1605069443808)(D:\software\typora\workplace\imgs_data_structure_arraylist\9.png)]

package com.lcz.array;
@SuppressWarnings("unchecked")
public class ArrayList<E> {

// 成员变量
int size;
E[] elements;
// 常量
private static final int DEFAULT_CAPATICY = 10;
private static final int DEFAULT_NOT_FOUNT = -1;
// 无参构造函数
public ArrayList() {

this(DEFAULT_CAPATICY);
}
// 有参构造函数
public ArrayList(int capaticy) {

capaticy = capaticy > DEFAULT_CAPATICY? capaticy:DEFAULT_CAPATICY;
elements = (E[])new Object[capaticy];
}
/**
* 返回元素的数量
* @return
*/
public int size() {

return size;
}
/**
* 是否为空
* @return
*/
public boolean isEmpty() {

return size==0;
}
/**
* 是否包含某个元素
* @param element
* @return
*/
public boolean contains(E element) {

return indexOf(element) != DEFAULT_NOT_FOUNT;
}
/**
* 添加元素到最后面
* @param element
*/
public void add(E element) {

add(size,element);
}
private void outOfBounds(int index) {

throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);
}
private void rangeCheck(int index) {

if(index<0 || index>=size) {

outOfBounds(index);
}
}
private void rangeForCheck(int index) {

if(index<0 || index>size) {

outOfBounds(index);
}
}
/**
* 返回index位置对应的元素
* @param index
* @return
*/
public E get(int index) {

// 对其进行index判断
rangeCheck(index);
return elements[index];
}
/**
* 设置index位置的元素
* @param index
* @param element
* @return 原来的元素
*/
public E set(int index,E element) {

// 对其进行index判断
rangeCheck(index);
E oldElement = elements[index];
elements[index] = element;
return oldElement;
}
/**
* 往index位置添加元素
* @param index
* @param element
*/
public void add(int index,E element) {

// index判断可以往size位置添加
rangeForCheck(index);
// 对其进行判断是否需要扩容
checkForCapaticy(size+1);
// 对其添加元素
for(int i=size;i>index;i--) {

elements[i] = elements[i-1];
}
elements[index] = element;
size++;
}
/**
* 检查容量并扩容
*/
private void checkForCapaticy(int capaticy) {

int oldCapaticy = elements.length;
if(oldCapaticy >= capaticy) {

return;
}
// 扩容
// 新容量为旧容量的1.5倍
int newCapaticy = oldCapaticy + oldCapaticy >> 1;
E newElements[] = (E[])new Object[newCapaticy];
for(int i=0;i<size;i++) {

newElements[i] = elements[i];
}
elements = newElements;
}
/**
* 删除index位置对应的元素
* @param index
* @return
*/
public E remove(int index) {

// 对index进行判断
rangeCheck(index);
E oldElement = elements[index];
for(int i=index+1;i<size;i++) {

elements[i-1] = elements[i];
}
size --;
return oldElement;
}
/**
* 查看元素的索引
* @param element
* @return
*/
public int indexOf(E element) {

if(element == null) {

for(int i=0;i<size;i++) {

if(elements[i]==null)
return i;
}
}else {

for(int i=0;i<size;i++) {

if(element.equals(elements[i]))
return i;
}
}
return DEFAULT_NOT_FOUNT;
}
/**
* 清除所有元素
*/
public void clear() {

size = 0;
}
/**
* 重写toString方法
*/
@Override
public String toString() {

// TODO Auto-generated method stub
StringBuilder res = new StringBuilder();
res.append("size=").append(size).append(", [");
for(int i=0;i<size;i++) {

if(i!=0) {

res.append(", ");
}
res.append(elements[i]);
}
res.append("]");
return res.toString();
}
}

3.8 对象数组

  • 对象数组中存储的是对象的地址,而不是对象的值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-szwWzrZI-1605069443809)(D:\software\typora\workplace\imgs_data_structure_arraylist\10.png)]

  • 若对其改进,需对内存管理进行修改

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OV23yTrF-1605069443809)(D:\software\typora\workplace\imgs_data_structure_arraylist\11.png)]

3.9 null的处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s6iOuxQy-1605069443810)(D:\software\typora\workplace\imgs_data_structure_arraylist\12.png)]

3.10 java.util.ArrayList

  • JDK中内置了一个动态数组类:java.util.ArrayList

3.11 ArrayList是否能进一步优化?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sI3xPdn5-1605069443811)(D:\software\typora\workplace\imgs_data_structure_arraylist\13.png)]

对ArrayList添加一个first记录第一个位置的索引,这样不需要释放内存。(可参考后续的循环双端队列设计)

二、 链表LinkedList

1.单向链表

  • 动态数组有明显的缺点-可能会造成内存空间的大量浪费
  • 能否用到多少就申请多少内存?
  • 链表就可以办到这一点
  • 链表是一种链式存储的线性表,所有元素的内存地址不一定是连续的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1eQet9BO-1605069443812)(D:\software\typora\workplace\imgs_data_structure_arraylist\14.png)]

1.1 链表的设计

  • 一个size,一个first指针

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O6l0KI4Z-1605069443813)(D:\software\typora\workplace\imgs_data_structure_arraylist\15.png)]

1.2 接口设计

  • ArrayList和LinkedList方法作用相同,共同引申一个接口List
  • 但ArrayList和LinkedList有几个方法实现相同,有几个方法实现不相同,这时候添加一个abstract类
  • 最后arraylist和linkedlist继承抽象类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jsRFQ5bx-1605069443813)(D:\software\typora\workplace\imgs_data_structure_arraylist\16.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-67iL8Gic-1605069443814)(D:\software\typora\workplace\imgs_data_structure_arraylist\17.png)]

1.3 重要接口实现

1.3.1 清空元素-clear()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eZC6r4vx-1605069443815)(D:\software\typora\workplace\imgs_data_structure_arraylist\18.png)]

@Override
public void clear() {

// TODO Auto-generated method stub
size = 0;
first = null;
}

1.3.2 添加元素-add(int index,E element)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8UcpIjbJ-1605069443816)(D:\software\typora\workplace\imgs_data_structure_arraylist\19.png)]

  • node方法用于获取index位置的节点
/**
* node方法用于获取index位置的结点
* @param index
* @return
*/
private Node<E> node(int index){

rangeCheck(index);
Node<E> node = first;
for(int i=0;i<index;i++) {

node = node.next;
}
return node;
}
  • 添加元素 注意index 0 的位置
@Override
public void add(int index, E element) {

rangeForAddCheck(index);
// TODO Auto-generated method stub
if(index == 0) {

first = new Node(element,first);
}else {

Node<E> pre = node(index-1);
pre.next = new Node(element,pre.next);
}
size ++;
}

1.3.3 删除元素-remove(int index)

@Override
public E remove(int index) {

// TODO Auto-generated method stub
rangeCheck(index);
Node<E> res = first;
if(index == 0 ) {

res = first;
first = first.next;
}else {

Node<E> pre = node(index-1);
res = pre.next;
pre.next = pre.next.next;
}
size--;
return res.element;
}

1.3.4 indexOf & toString方法

@Override
public int indexOf(E element) {

// TODO Auto-generated method stub
if(element==null) {

Node<E> node = first;
for(int i=0;i<size;i++) {

if(node.element==null)
return i;
node = node.next;
}
}else {

Node<E> node = first;
for(int i=0;i<size;i++) {

if(element.equals(node.element))
return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* node方法用于获取index位置的结点
* @param index
* @return
*/
private Node<E> node(int index){

rangeCheck(index);

2.双向链表

  • 之前所学的链表,也被称为单向链表
  • 使用双向链表可以提升链表的综合性能

2.1 双向链表的设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ISlq2n9h-1605069443817)(D:\software\typora\workplace\imgs_data_structure_arraylist\20.png)]

  • 当双向链表只有一个元素时

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SP6y7Gk7-1605069443818)(D:\software\typora\workplace\imgs_data_structure_arraylist\21.png)]

2.2.重要接口的设计

2.2.1 获取index对应位置的节点

**
* 获取index位置对应的节点对象
* @param index
* @return
*/
private Node<E> node(int index) {

rangeCheck(index);
if (index < (size >> 1)) {

Node<E> node = first;
for (int i = 0; i < index; i++) {

node = node.next;
}
return node;
} else {

Node<E> node = last;
for (int i = size - 1; i > index; i--) {

node = node.prev;
}
return node;
}
}

2.2.2 添加元素-add(int index,E element)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qFmj6xQL-1605069443819)(D:\software\typora\workplace\imgs_data_structure_arraylist\22.png)]

  • 添加考虑几种情况
  • 尾部插入和空加入
  • 正常加入
  • 头部加入
 @Override
public void add(int index, E element) {

rangeCheckForAdd(index);
// size == 0
// index == 0
if (index == size) {
 // 往最后面添加元素
Node<E> oldLast = last;
last = new Node<>(oldLast, element, null);
if (oldLast == null) {
 // 这是链表添加的第一个元素
first = last;
} else {

oldLast.next = last;
}
} else {

Node<E> next = node(index);
Node<E> prev = next.prev;
Node<E> node = new Node<>(prev, element, next);
next.prev = node;
if (prev == null) {
 // index == 0
first = node;
} else {

prev.next = node;
}
}
size++;
}

2.2.3 删除元素-remove(int index,E element)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xxZUgiiY-1605069443820)(D:\software\typora\workplace\imgs_data_structure_arraylist\23.png)]

  • 添加考虑几种情况
  • prev为空
  • next为空
 @Override
public E remove(int index) {

rangeCheck(index);
Node<E> node = node(index);
Node<E> prev = node.prev;
Node<E> next = node.next;
if (prev == null) {
 // index == 0
first = next;
} else {

prev.next = next;
}
if (next == null) {
 // index == size - 1
last = prev;
} else {

next.prev = prev;
}
size--;
return node.element;
}

2.2.4 双向链表 vs 动态数组

  • 动态数组:开辟、销毁内存空间的次数相对较少,但可能造成内存空间的浪费

  • 双向链表:开辟、销毁内存空间的次数相比较多,但不会造成内存空间的浪费

  • 如果频繁在尾部进行添加、删除操作,动态数组、双向链表均可选择

  • 如果频繁在头部进行添加、删除操作,建议选择使用双向链表

  • 如果有频繁的在任意位置添加、删除操作,建议选择使用双向链表

  • 如果有频繁的查询操作,建议选择使用动态数组

  • 如果有了双向链表,单向链表是否就没有任何用处了?

    • 并非如此,在哈希表的设计中就用到了单链表
    • hashmap使用的是单链表;而在linkedhashmap中使用的双链表

3.单向循环链表

3.1 单向循环链表的设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gxhz0nDd-1605069443820)(D:\software\typora\workplace\imgs_data_structure_arraylist\24.png)]

单向循环链表-只有1个节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kPnF8aHb-1605069443821)(D:\software\typora\workplace\imgs_data_structure_arraylist\25.png)]

3.2 重要接口设计

3.2.1 单向循环链表-add(int index, E element)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q6M3n1ji-1605069443822)(D:\software\typora\workplace\imgs_data_structure_arraylist\26.png)]

@Override
public void add(int index, E element) {

rangeCheckForAdd(index);
if (index == 0) {

Node<E> newFirst = new Node<>(element, first);
// 拿到最后一个节点
Node<E> last = (size == 0) ? newFirst : node(size - 1);
last.next = newFirst;
first = newFirst;
} else {

Node<E> prev = node(index - 1);
prev.next = new Node<>(element, prev.next);
}
size++;
}

3.2.2 单向循环链表-remove(int index)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ijJ17NIY-1605069443824)(D:\software\typora\workplace\imgs_data_structure_arraylist\27.png)]

@Override
public E remove(int index) {

rangeCheck(index);
Node<E> node = first;
if (index == 0) {

if (size == 1) {

first = null;
} else {

Node<E> last = node(size - 1);
first = first.next;
last.next = first;
}
} else {

Node<E> prev = node(index - 1);
node = prev.next;
prev.next = node.next;
}
size--;
return node.element;
}

单向循环链表,主要考虑头结点位置的插入和删除,插入的时候头结点是否有节点

4.双向循环链表

4.1 双向循环链表的设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AqERnngv-1605069443825)(D:\software\typora\workplace\imgs_data_structure_arraylist\28.png)]

双向循环链表-只有一个节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h5by9NAv-1605069443825)(D:\software\typora\workplace\imgs_data_structure_arraylist\29.png)]

4.2 重要接口设计

4.2.1 双向循环链表-add(int index,E element)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r6XOC5qJ-1605069443826)(D:\software\typora\workplace\imgs_data_structure_arraylist\30.png)]

@Override
public void add(int index, E element) {

rangeCheckForAdd(index);
// size == 0
// index == 0
if (index == size) {
 // 往最后面添加元素
Node<E> oldLast = last;
last = new Node<>(oldLast, element, first);
if (oldLast == null) {
 // 这是链表添加的第一个元素
first = last;
first.next = first;
first.prev = first;
} else {

oldLast.next = last;
first.prev = last;
}
} else {

Node<E> next = node(index);
Node<E> prev = next.prev;
Node<E> node = new Node<>(prev, element, next);
next.prev = node;
prev.next = node;
if (next == first) {
 // index == 0
first = node;
}
}
size++;
}

4.2.2 双向循环链表-remove(int index)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x7qva57E-1605069443827)(D:\software\typora\workplace\imgs_data_structure_arraylist\31.png)]

@Override
public E remove(int index) {

rangeCheck(index);
return remove(node(index));
}
private E remove(Node<E> node) {

if (size == 1) {

first = null;
last = null;
} else {

Node<E> prev = node.prev;
Node<E> next = node.next;
prev.next = next;
next.prev = prev;
if (node == first) {
 // index == 0
first = next;
}
if (node == last) {
 // index == size - 1
last = prev;
}
}
size--;
return node.element;
}

4.3 约瑟夫环的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5qoFJK90-1605069443828)(D:\software\typora\workplace\imgs_data_structure_arraylist\32.png)]

在单向循环链表中增设一个成员变量,三个成员方法来解决

  • current:用于指向某个节点
  • void reset(): 让current指向头结点first
  • E next(): 让current往后走一步,也就是current = current.next
  • E remove():删除current指向的节点,删除成功后让current指向下一个节点
public void reset() {

current = first;
}
public E next() {

if (current == null) return null;
current = current.next;
return current.element;
}
public E remove() {

if (current == null) return null;
Node<E> next = current.next;
E element = remove(current);
if (size == 0) {

current = null;
} else {

current = next;
}
return element;
}

三、栈(Stack)

  • 栈是一种特殊的线性表,只能在一端进行操作
    • 往栈中添加元素的操作,一般叫做push,入栈
    • 从栈中移除元素的操作,一般叫做pop,出栈
    • 后进先出的原则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-REY2RlTl-1605069443828)(D:\software\typora\workplace\imgs_data_structure_arraylist\33.png)]

1.栈的接口设计

  • int size(); // 元素的数量
  • boolean isEmpty(); // 是否为空
  • void push(E element); // 入栈
  • E pop(); //出栈
  • E peek(); //获取栈顶元素
  • void clear(); //清空

栈的实现本次基本之前的动态数组、链表。不采取继承的方式,而是基于成员变量的方法。

package com.mj;
import com.mj.list.ArrayList;
import com.mj.list.List;
public class Stack<E> {

private List<E> list = new ArrayList<>();
public void clear() {

list.clear();
}
public int size() {

return list.size();
}
public boolean isEmpty() {

return list.isEmpty();
}
public void push(E element) {

list.add(element);
}
public E pop() {

return list.remove(list.size() - 1);
}
public E top() {

return list.get(list.size() - 1);
}
}

2.java.util.Stack

java中的实现是通过继承Vector来实现的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AKHMCHXT-1605069443829)(D:\software\typora\workplace\imgs_data_structure_arraylist\34.png)]

3.栈的应用

  • 实现方式:通过两个栈来实现,一个栈来存取,另外一个栈存取前面一个栈出栈的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8wUC4V80-1605069443830)(D:\software\typora\workplace\imgs_data_structure_arraylist\35.png)]

4.栈的练习题

1.有效括号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-074yNDUs-1605069443830)(D:\software\typora\workplace\imgs_data_structure_arraylist\36.png)]

解题思路:

  • 1.遇见左括号,将左字符入栈

  • 2.遇见右括号

    • 如果栈是空的,说明括号无效
    • 如果栈不为空,将栈顶字符出栈,与右字符匹配
      • 如果匹配不成功,则括号无效
      • 如果左右字符匹配,继续扫描下一个字符
  • 3.所有字符扫描完毕后

    • 栈为空,说明括号无效

    • 栈不为空,说明括号无效

class Solution {

public boolean isValid(String s) {

HashMap<Character,Character> hashMap = new HashMap<>();
hashMap.put('(',')');
hashMap.put('{','}');
hashMap.put('[',']');
Stack<Character> stack = new Stack<>();
for(int i=0;i<s.length();i++){

char ch = s.charAt(i);
if(hashMap.containsKey(ch)){

stack.push(ch);
}else{

if(stack.isEmpty()){

return false;
}else{

if(ch!=hashMap.get(stack.pop())){

return false;
}
}
}
}
return stack.isEmpty();
}
}

四、 队列(Queue)

队列是一种特殊的线性表,只能在头尾两端进行操作。

  • 队尾(rear):只能从队尾添加元素,一般叫做enQueue,入队
  • 对头(front):只能从对头移除元素,一般叫做deQueue,出队
  • 先进先出的原则,First In First Out,FIFO

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M3gJ0CxG-1605069443831)(D:\software\typora\workplace\imgs_data_structure_arraylist\37.png)]

1.队列

1.1 队列的端口设计

  • int size(): // 元素的数量
  • boolean isEmpty(); //是否为空
  • void clear(); //清空
  • void enQueue(E element); // 入队
  • E deQueue(); //出队
  • E front(); //获取队列的头元素

队列本次实现使用双向链表来实现,因为队列主要是往头尾操作元素。

1.2 队列的接口实现

public class Deque<E> {

private List<E> list = new LinkedList<E>();
/**
* 返回队列的长度
* @return
*/
public int size() {

return list.size();
}
/**
* 返回队列是否为空的判断
* @return
*/
public boolean isEmpty() {

return list.isEmpty();
}
/**
* 清空队列中的元素
*/
public void clear() {

list.clear();
}
/**
*入队操作
* @param element
*/
public void enQueue(E element) {

list.add(element);
}
/**
* 出队操作
* @return
*/
public E deQueue() {

E element = list.remove(0);
return element;
}
/**
* 获取队列的头元素
* @return
*/
public E front() {

E element = list.get(0);
return element;
}
}

1.3 队列的练习-用栈实现队列

解题思路:

  • 准备两个栈:inStack, outStack
  • 入队时,push到inStack中
  • 出队时,
    • 如果outStack为空,将inStack所有元素逐一弹出,push到outStack,outStack弹出栈顶元素
    • 如果outStack不为空,outStack弹出栈顶元素
class MyQueue {

/** Initialize your data structure here. */
Stack<Integer> stack_first = null;
Stack<Integer> stack_second = null;
/**
* 初始化队列
*/
public MyQueue() {

stack_first = new Stack<Integer>();
stack_second = new Stack<Integer>();
}
/**
* 将元素x推到队列的末尾
*/
/** Push element x to the back of queue. */
public void push(int x) {

// 直接入栈1
stack_first.push(x);
}
/**
* 从队列的开头移除并返回元素
*/
/** Removes the element from in front of queue and returns that element. */
public int pop() {

// 判断栈2
if(stack_second.isEmpty()) {

// 栈2为空的话
// 先将栈1都弹出来
while(!stack_first.isEmpty()) {

stack_second.push(stack_first.pop());
}
return stack_second.pop();
}else {

// 栈2不为空的话
return stack_second.pop();
}
}
/**
* 返回队列开头的元素
*/
/** Get the front element. */
public int peek() {

if(stack_second.isEmpty()) {

while(!stack_first.isEmpty()) {

stack_second.push(stack_first.pop());
}
return stack_second.peek();
}else {

return stack_second.peek();
}
}
/**
* 判断队列是否为空
*/
/** Returns whether the queue is empty. */
public boolean empty() {

return stack_first.isEmpty()&&stack_second.isEmpty();
}
}

1.4 java.util.Queue的实现

  • 入队操作:offer
  • 出队操作:poll
  • 获取对头:peek

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bWU5GjOM-1605069443832)(D:\software\typora\workplace\imgs_data_structure_arraylist\38.png)]

2. 双端队列(Deque)

双端队列是能在头尾两端添加、删除的队列。

2.1 双端队列重要接口设计

  • int size(); //元素的数量
  • boolean isEmpty(); //是否为空
  • void clear(); //清空
  • void enQueueRear(E element); //从队尾入队
  • E deQueueFront(); //从队头出队
  • void enQueueFront(); //从队头入队
  • E deQueueRear(); //从队尾出队
  • E front(); //获取队列的头元素
  • E rear(); //获取队列的尾元素

2.2 双端队列重要接口实现

public class Deque<E> {

private List<E> list = new LinkedList<E>();
/**
* 获取队列的数量
* @return
*/
public int size() {

return list.size();
}
/**
* 判断队列是否为空
* @return
*/
public boolean isEmpty() {

return list.isEmpty();
}
/**
* 清空队列
*/
public void clear() {

list.clear();
}
/**
* 从队尾入队
* @param element
*/
public void enQueueRear(E element) {

list.add(element);
}
/**
* 从队头入队
* @param element
*/
public void enQueueFront(E element) {

list.add(0,element);
}
/**
* 从队头出队
*/
public E deQueueFront() {

return list.remove(0);
}
/**
* 从队尾出队
*/
public E deQueueRear() {

return list.remove(list.size()-1);
}
/**
* 获取队列的头元素
* @return
*/
public E front() {

return list.get(0);
}
/**
* 获取队列的尾元素
* @return
*/
public E rear() {

return list.get(list.size()-1);
}
}

3. 循环队列(Circle Queue)

队列底层也可以使用动态数组实现,并且各项接口也可以优化到O(1)的时间复杂度。

这个用数组实现并且优化之后的队列也叫作:循环队列。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GemUPkSK-1605069443833)(D:\software\typora\workplace\imgs_data_structure_arraylist\39.png)]

3.1 循环队列的端口设计

  • int size(); //获取队列的大小
  • boolean isEmpty(); //判断队列是否为空
  • void clear(); //清空队列
  • void enQueue(); //入队
  • E deQuque(); //出队
  • E front(); //队头元素

3.2 循环队列的接口实现

public class CircleQueue<E> {

int size;
int front;
E elements[];
private static final int DEFAULT_CAPATICY = 10;
/**
* 构造函数
*/
public CircleQueue() {

elements = (E[])new Object[DEFAULT_CAPATICY];
}
/**
* 获取循环队列的大小
* @return
*/
public int size() {

return size;
}
/**
* 判断队列是否为空
* @return
*/
public boolean isEmpty() {

return size==0;
}
/**
* 清空队列
*/
public void clear() {

for(int i=0;i<size;i++) {

elements[index(i)] = null;
}
front = 0;
size = 0;
}
/**
* 入队
*/
public void enQueue(E element) {

// 先看容量
ensureCapaticy(size+1);
elements[index(size)] = element;
size++;
}
/**
* 出队
*/
public E deQueue() {

E frontElement = elements[front];
elements[front] = null;
front = index(1);
size--;
return frontElement;
}
/**
* 返回队头元素
* @return
*/
public E front() {

return elements[front];
}
/**
* 工具类
*/
private int index(int index) {

return (front+index)%elements.length;
}
/**
* 容量扩充
*/
private void ensureCapaticy(int capaticy) {

int oldCapaticy = elements.length;
if(oldCapaticy>=capaticy)
return;
int newCapaticy = oldCapaticy + oldCapaticy >> 1;
E[] newElements = (E[])new Object[newCapaticy];
for(int i=0;i<size;i++) {

newElements[i] = elements[index(i)];
}
elements = newElements;
// 重置front
front = 0;
}
}

4. 循环双端队列

  • 循环双端队列:可以进行两端添加、删除操作的循环队列。

4.1 循环双端队列的端口设计

  • int size(); //获取队列的大小
  • boolean isEmpty(); //判断队列是否为空
  • void clear(); //清空队列
  • void enQueueRear(); //从队尾入队
  • void enQueueFront(); //队头入队
  • E deQuqueFront(); /从队头/出队
  • E deQuqueRear(); //从队尾出队
  • E front(); //队头元素
  • E rear(); //队尾元素

4.2 循环双端队列的端口实现

public class CircleDeque<E> {

int size;
int front;
E elements[];
private static final int DEFAULT_CAPATICY = 10;
/**
* 构造函数
*/
public CircleDeque() {

elements = (E[])new Object[DEFAULT_CAPATICY];
}
/**
* 获取循环队列的大小
* @return
*/
public int size() {

return size;
}
/**
* 判断队列是否为空
* @return
*/
public boolean isEmpty() {

return size==0;
}
/**
* 清空队列
*/
public void clear() {

for(int i=0;i<size;i++) {

elements[index(i)] = null;
}
front = 0;
size = 0;
}
/**
* 从队尾入队
*/
public void enQueueRear(E element) {

// 先看容量
ensureCapaticy(size+1);
elements[index(size)] = element;
size++;
}
/**
* 从队头入队
* @param element
*/
public void enQueueFront(E element) {

// 容量
ensureCapaticy(size+1);
front = index(-1);
elements[front] = element;
size++;
}
/**
* 从队头出队
*/
public E deQueueFront() {

E frontElement = elements[front];
elements[front] = null;
front = index(1);
size--;
return frontElement;
}
/**
* 从队尾出队
* @return
*/
public E deQueueRear() {

E rearElement = elements[index(size-1)];
elements[index(size-1)] = null;
size--;
return rearElement;
}
/**
* 返回队头元素
* @return
*/
public E front() {

return elements[front];
}
/**
* 返回队尾元素
*/
public E rear() {

return elements[index(size-1)];
}
/**
* 工具类
*/
private int index(int index) {

index = front + index;
if(index < 0 ) {

return index + elements.length;
}
return index%elements.length;
}
/**
* 容量扩充
*/
private void ensureCapaticy(int capaticy) {

int oldCapaticy = elements.length;
if(oldCapaticy>=capaticy)
return;
int newCapaticy = oldCapaticy + oldCapaticy >> 1;
E[] newElements = (E[])new Object[newCapaticy];
for(int i=0;i<size;i++) {

newElements[i] = elements[index(i)];
}
elements = newElements;
// 重置front
front = 0;
}
}

5. 队列的应用-用队列实现栈

实现思路:

  • 准备两个队列用于实现栈 一个queue_in 一个queue_out
    • 入栈的话 queue_in接收元素 并将queue_out中的元素全部排出并接收 之后queue_in 和queue_out互换
    • 出栈的话 queue_out出队列即可
class MyStack {

Queue<Integer> queue_in;
Queue<Integer> queue_out;
/** Initialize your data structure here. */
public MyStack() {

queue_in = new LinkedList<>();
queue_out = new LinkedList<>();
}
/** Push element x onto stack. */
public void push(int x) {

queue_in.offer(x);
while(!queue_out.isEmpty()){

queue_in.offer(queue_out.poll());
}
Queue temp_queue = queue_in;
queue_in = queue_out;
queue_out = temp_queue;
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {

return queue_out.poll();
}
/** Get the top element. */
public int top() {

return queue_out.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {

return queue_out.isEmpty();
}
}

ength;
}
return index%elements.length;
}

/**
* 容量扩充
*/
private void ensureCapaticy(int capaticy) {
int oldCapaticy = elements.length;
if(oldCapaticy>=capaticy)
return;
int newCapaticy = oldCapaticy + oldCapaticy >> 1;
E[] newElements = (E[])new Object[newCapaticy];
for(int i=0;i<size;i++) {
newElements[i] = elements[index(i)];
}
elements = newElements;
// 重置front
front = 0;
}

}


## 5. 队列的应用-用队列实现栈
> 实现思路:
>
> - 准备两个队列用于实现栈 一个queue_in 一个queue_out
> - 入栈的话 queue_in接收元素 并将queue_out中的元素全部排出并接收 之后queue_in 和queue_out互换
> - 出栈的话 queue_out出队列即可
```java
class MyStack {
Queue<Integer> queue_in;
Queue<Integer> queue_out;
/** Initialize your data structure here. */
public MyStack() {
queue_in = new LinkedList<>();
queue_out = new LinkedList<>();
}
/** Push element x onto stack. */
public void push(int x) {
queue_in.offer(x);
while(!queue_out.isEmpty()){
queue_in.offer(queue_out.poll());
}
Queue temp_queue = queue_in;
queue_in = queue_out;
queue_out = temp_queue;
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
return queue_out.poll();
}
/** Get the top element. */
public int top() {
return queue_out.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return queue_out.isEmpty();
}
}

在这里插入图片描述

发表回复