1.Hash基础
1.1.Hash的概念和基本特征
哈希(Hash)也称为散列,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,这个输出值就是散列值。
哈希计算取模流程图
假如我要测试13在不在这里结构里,则同样使用上面的公式来进行,很明显13 模7=6,我们直接访问array[6]这个位置,很明显是在的,所以返回true。
假如我要测试20在不在这里结构里,则同样使用上面的公式来进行,很明显20模7=6,我们直接访问array[6]这个位置,但是只有6和13,所以返回false。
理解这个例子我们就理解了Hash是如何进行最基本的映射的,还有就是为什么访问的时间复杂度为O(1)。
1.2碰撞处理方法
两个不同的输入值,根据同一散列函数计算出的散列值相同的现象叫做碰撞
1.2.1开放定址法
开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,不是在原处创建一个链表而是按照一定规则寻找周围可用的地址进行插
入。所以只要散列表足够大,空的散列地址总能找到,并将记录存入。
**缺点: **容易产生堆积问题;不适于大规模的数据存储;散列函数的设计对冲突会有很大的影响;插入时可能会出现多次冲突的现象,删除
的元素是多个冲突元素中的一个,需要对后面的元素作处理,实现较复杂;结点规模很大时会浪费很多空间
1.2.2链表法
将哈希表的每个单元作为链表的头结点,所有哈希地址为i的元素构成一个同义词链表。即发生冲突时就把该关键字链在以该单元为头结点
的链表的尾部。例如:
2.队列基础知识
2.1.队列的概念和基本特征
队列的特点是节点的排队次序和出队次序按入队时间先后确定,即先入队者先出队,后入队者后出队,即我们常说的FIFO(first in first out)
先进先出。队列实现方式也有两种形式,基于数组和基于链表。
(1)基于链表实现
public class LinkQueue {
private Node front;
private Node rear;
private int size;
public LinkQueue() {
this.front = new Node(0);
this.rear = new Node(0);
}
/**
* 入队
*/
public void push(int value) {
Node newNode = new Node(value);
Node temp = front;
while (temp.next != null) {
temp = temp.next;
}
temp.next = newNode;
rear = newNode;
size++;
}
/**
* 出队
*/
public int pull() {
if (front.next == null) {
System.out.println("队列已空");
}
Node firstNode = front.next;
front.next = firstNode.next;
size--;
return firstNode.data;
}
/**
* 遍历队列
*/
public void traverse() {
Node temp = front.next;
while (temp != null) {
System.out.print(temp.data + "\t");
temp = temp.next;
}
}
static class Node {
public int data;
public Node next;
public Node(int data) {
this.data = data;
}
}
//测试main方法
public static void main(String[] args) {
LinkQueue linkQueue = new LinkQueue();
linkQueue.push(1);
linkQueue.push(2);
linkQueue.push(3);
System.out.println("第一个出队的元素为:" + linkQueue.pull());
System.out.println("队列中的元素为:");
linkQueue.traverse();
}
}
(2)基于数组实现
- 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图,其中maxSize是该队列的最大容量。
- 因为队列的输出、输入是分别从前后端来处理,因此需要两个变量front及rear分别记录队列前后端的下标,front 会随着数据输出而改变,而rear则是随着数据输入而改变
- 当我们将数据存入队列时称为”push”,push的处理需要有两个步骤:思路分析
- 将尾指针往后移:rear+1,当 front— rear【空】
- 若尾指针 rear 小于队列的最大下标maxSize-1,则将数据存入rear所指的数组元素中,否则无法存入数据。 rear=maxSize-1[队列满]
public class ArrayQueue {
private int maxSize; //表示数组的最大容量
private int rear; //指向队列尾部的尾指针
private int front; //指向队列头部的头指针
private int[] queue; //该数组用于存放数据,模拟队列
public ArrayQueue(int maxSize) {
this.maxSize = maxSize;
this.front = -1;
this.rear = -1;
queue = new int[maxSize];
}
public boolean isFull() {
return rear == maxSize - 1;
}
public boolean isEmpty() {
return front == rear;
}
public void push(int data) {
if (isFull()) {
System.out.println("队列已满");
return;
}
rear++;
queue[rear] = data;
}
public int pull() {
//如果队列为空
if (isEmpty()) {
//return -1;可能获取的值为-1
//使用抛出异常进行解决
throw new RuntimeException("队列空,不能取数据");
//不用写return,因为抛出异常后会立刻中止程序
}
front++;
return queue[front];
}
//显示队列的数据,显队列
public void displayQueue() {
//如果队列为空
if (isEmpty()) {
System.out.println("队列为空,显示个寂寞!");
return;
}
for (int i = 0; i < queue.length; i++) {
System.out.printf("queue[%d]=%d\n ", i, queue[i]);
}
}
//显示队列的头数据
public int showHead() {
//如果无数据
if (isEmpty()) {
System.out.println("队列空的,没有数据~~");
throw new RuntimeException("队列空的,没有数据~~");
}
return queue[front + 1];
}
}
测试
public static void main(String[] args) {
//测试一把
//创建一个队列
ArrayQueue queue = new ArrayQueue(3);
char key = ' ';//接收用户输入
Scanner scanner = new Scanner(System.in);
boolean loop = true;
//输出一个菜单
while (loop) {
System.out.println();
System.out.println("s(show): 显示队列");
System.out.println("e(exit): 退出队列");
System.out.println("a(add): 添加数据到队列");
System.out.println("g(get): 从队列取出数据");
System.out.println("h(head): 查看队列头的数据");
key = scanner.next().charAt(0); //接收一个字符
switch (key) {
case 's'://显示队列
System.out.println("显示队列如下:");
queue.displayQueue();
break;
case 'a'://添加队列
System.out.println("请输入一个数:");
int n = scanner.nextInt(); //输入n值
queue.push(n);
break;
case 'g'://取出数据
try {
int res = queue.pull();
System.out.printf("取出的数据是%d\n", res);
} catch (Exception e) {
System.out.println(e.getMessage());//输出异常信息
}
break;
case 'h'://查看队列头数据
try {
int res = queue.showHead();
System.out.printf("队列头数据是%d\n", res);
} catch (Exception e) {
System.out.println(e.getMessage());//输出异常信息
}
break;
case 'e'://退出
scanner.close();//不关闭会显示异常
loop = false;
break;
default:
System.out.println("选项不合法");
break;
}
}
System.out.println("程序退出!");
}
问题分析并优化:
- 目前上边代码中的数组只能使用一次,不能复用。原因:控制角标的指针rear和front,增大后无法自动变小
- 解决方法:将原来代码使用算法改进成环形队列 取模:%。通过取模解决了指针rear和front变大后变小
# 思路
# 1.front变量的含义做一个调整: front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素front的初始值=0
# 2.rear变量的含义做一个调整: rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间做为约定rear 的初始值=0
# 3.当队列满时,条件是 (rear +1)%maxSize=front[满]
# 4.对队列为空的条件,rear == front空
# 5.当我们这样分析,队列中有效的数据的个数 (rear+maxSize-front)%maxSize,我们就可以在原来的队列上改得到,一个环形队列
public class ArrayQueue {
private int maxSize; //表示数组的最大容量
private int rear; //指向队列尾部的尾指针
private int front; //指向队列头部的头指针
private int[] queue; //该数组用于存放数据,模拟队列
public ArrayQueue(int maxSize) {
this.maxSize = maxSize;
queue = new int[maxSize];
}
public boolean isFull() {
return (rear + 1) % maxSize == front;
}
public boolean isEmpty() {
return front == rear;
}
public void push(int data) {
if (isFull()) {
System.out.println("队列已满");
return;
}
queue[rear] = data;
rear = (rear + 1) % maxSize;
}
public int pull() {
//如果队列为空
if (isEmpty()) {
//return -1;可能获取的值为-1
//使用抛出异常进行解决
throw new RuntimeException("队列空,不能取数据");
//不用写return,因为抛出异常后会立刻中止程序
}
int value = queue[front];
front = (front + 1) % maxSize;
return value;
}
public int getArrsize() {
return (rear + maxSize - front) % maxSize;
}
//显示队列的数据,显队列
public void displayQueue() {
//如果队列为空
if (isEmpty()) {
System.out.println("队列为空,显示个寂寞!");
return;
}
for (int i = 0; i < front + getArrsize(); i++) {
System.out.printf("queue[%d]=%d\n ", i % maxSize, queue[i % maxSize]);
}
}
//显示队列的头数据
public int showHead() {
//如果无数据
if (isEmpty()) {
System.out.println("队列空的,没有数据~~");
return -1;
}
return queue[front];
}
}