链表
链表是什么
- 链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,有一系列结点(地址)组成,结点可动态的生成。
- 结点包括两个部分:
(1)存储数据元素的数据域(内存空间)
(2)存储指向下一个结点地址的指针域。 - 链表分类:
(1)单链表 (2)双链表 (3)单向循环链表 (4)双向循环链表 - 链表作用:实现数据元素的存储按一定顺序储存,允许在任意位置插入和删除结点
认识常见链表
单链表
单链表有两个较特殊节点,头结点和尾节点。头结点用来记录链表的基地址,可以用来遍历整条链表。尾结点的指针不是指向下一个节点而是指向一个空地址NULL,表示链表上的最后一个节点。
- 代码表示
public static class Node {
// 值
public int value;
//下一个节点属性
public Node next;
public Node(int data) {
value = data;
}
}
- 常见面试题:单向链表反转
public static Node reverseLinkedList(Node head) {
Node pre = null; //上节点
Node next = null; //下节点
while (head != null) {
next = head.next; //暂存下节点
head.next = pre; // 将上节点赋值为本节点的下节点
pre = head; //将本节点设置为上节点
head = next; //将本节点设置为下节点
}
return pre;
}
双向链表
双向链表支持两个方向,每个节点有一个后继指针next指向后面的节点,还有一个前驱指针指向前面的节点。
- 代码表示
public static class DoubleNode {
public int value;
public DoubleNode last;
public DoubleNode next;
public DoubleNode(int data) {
value = data;
}
}
- 常见面试题:双向链表反转
public static DoubleNode reverseDoubleList(DoubleNode head) {
DoubleNode pre = null; //上节点
DoubleNode next = null; //下节点
while (head != null) {
next = head.next; //暂存下节点
head.next = pre; // 将上节点赋值为本节点的下节点
head.last = next; // 将下节点赋值为上节点
pre = head; //将本节点设置为上节点
head = next; //将本节点设置为下节点
}
return pre;
}
循环链表:单向,双向
将单链表中终端节点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相连的单链表称为循环链表。
单链表判断p->next是否为空,循环链表则是p->next是否等于头结点。
栈和队列的实现
逻辑概念
栈:数据先进后出,犹如弹匣
队列:数据先进先出,好似排队
栈
栈:数据先进后出,犹如弹匣 是一个线性表的结构。
- 栈的操作只能在这个线性表的表尾进行。
- 对于栈来说,这个表尾称为栈的栈顶(top),相应的表头称为栈底(bottom)。
(1)因为栈的本质是一个线性表,线性表有两种存储形式,那么栈也有分为栈的顺序存储结构和栈的链式存储结构。
(2)最开始栈中不含有任何数据,叫做空栈,此时栈顶就是栈底。然后数据从栈顶进入,栈顶栈底分离,整个栈的当前容量变大。数据出栈时从栈顶弹出,栈顶下移,整个栈的当前容量变小。
队列
只允许在一端插入数据操作,在另一端进行删除数据操作的特殊线性表;进行插入操作的一端称为队尾(入队列),进行删除操作的一端称为队头(出队列);队列具有先进先出(FIFO)的特性。
队列的分类:
- 顺序队列 (数组实现)
(1)队头不动,出队列时队头后的所有元素向前移动
缺陷:操作是如果出队列比较多,要搬移大量元素
(2)队头移动,出队列时队头向后移动一个位置
如果还有新元素进行入队列容易造成假溢出。
- 假溢出:顺序队列因多次入队列和出队列操作后出现的尚有存储空间但不能进行入队列操作的溢出。
- 真溢出:顺序队列的最大存储空间已经存满二又要求进行入队列操作所引起的溢出。
- 循环队列 (数组实现)
该方式实现如果采用两个指针相互追赶的模式,很难界定追赶上的尺度,所以在这里设置一个数组大小size,pop指针和pull指针之间的差值,只要小于size即认为队列未满。
- 链式队列 (链表实现)
栈和队列的实际实现
双链表实现
public class DoubleEndsQueueToStackAndQueue {
//操作节点
public static class Node<T> {
public T value;
public Node<T> last;
public Node<T> next;
public Node(T data) {
value = data;
}
}
//操作队列
public static class DoubleEndsQueue<T> {
public Node<T> head;
public Node<T> tail;
//添加头
public void addFromHead(T value) {
Node<T> cur = new Node<T>(value);
if (head == null) {
head = cur;
tail = cur;
} else {
cur.next = head;
head.last = cur;
head = cur;
}
}
//添加尾
public void addFromBottom(T value) {
Node<T> cur = new Node<T>(value);
if (head == null) {
head = cur;
tail = cur;
} else {
cur.last = tail;
tail.next = cur;
tail = cur;
}
}
//弹出头
public T popFromHead() {
if (head == null) {
return null;
}
Node<T> cur = head;
if (head == tail) {
head = null;
tail = null;
} else {
head = head.next;
cur.next = null;
head.last = null;
}
return cur.value;
}
//弹出尾
public T popFromBottom() {
if (head == null) {
return null;
}
Node<T> cur = tail;
if (head == tail) {
head = null;
tail = null;
} else {
tail = tail.last;
tail.next = null;
cur.last = null;
}
return cur.value;
}
public boolean isEmpty() {
return head == null;
}
}
// 实现栈
public static class MyStack<T> {
private DoubleEndsQueue<T> queue;
public MyStack() {
queue = new DoubleEndsQueue<T>();
}
public void push(T value) {
queue.addFromHead(value);
}
public T pop() {
return queue.popFromHead();
}
public boolean isEmpty() {
return queue.isEmpty();
}
}
//实现队列
public static class MyQueue<T> {
private DoubleEndsQueue<T> queue;
public MyQueue() {
queue = new DoubleEndsQueue<T>();
}
public void push(T value) {
queue.addFromHead(value);
}
public T poll() {
return queue.popFromBottom();
}
public boolean isEmpty() {
return queue.isEmpty();
}
}
}
数组实现
//环形队列
public class RingArray {
//数组极易实现栈,本身数组就是头进,取index=0,即为头出,即为栈。
//这里是通过环形数组实现队列的功能
public static class MyQueue {
private int[] arr;
private int pushi;
private int polli;
//该方式实现如果采用两个指针相互追赶的模式,很难界定追赶上的尺度,所以在这里设置一个数组大小size,pop指针和pull指针之间的差值,只要小于size即认为队列未满。
private int size;
private final int limit;
public MyQueue(int l) {
arr = new int[l];
pushi = 0;
polli = 0;
size = 0;
limit = l;
}
public void push(int value) {
if (size == limit) {
throw new RuntimeException("队列满了,不能再加了");
}
size++;
arr[pushi] = value;
pushi = nextIndex(pushi);
}
public int pop() {
if (size == 0) {
throw new RuntimeException("队列空了,不能再拿了");
}
size--;
int ans = arr[polli];
polli = nextIndex(pushi);
return ans;
}
public boolean isEmpty() {
return size == 0;
}
private int nextIndex(int i) {
return i < limit - 1 ? i + 1 : 0;
}
}
}
两个栈实现队列
public class TwoStacksImplementQueue {
public static class TwoStacksQueue {
public Stack<Integer> stackPush;
public Stack<Integer> stackPop;
public TwoStacksQueue() {
stackPush = new Stack<Integer>();
stackPop = new Stack<Integer>();
}
// push栈向pop栈倒入数据
private void pushToPop() {
if (stackPop.empty()) {
while (!stackPush.empty()) {
stackPop.push(stackPush.pop());
}
}
}
public void add(int pushInt) {
stackPush.push(pushInt);
pushToPop();
}
public int poll() {
if (stackPop.empty() && stackPush.empty()) {
throw new RuntimeException("Queue is empty!");
}
pushToPop();
return stackPop.pop();
}
public int peek() {
if (stackPop.empty() && stackPush.empty()) {
throw new RuntimeException("Queue is empty!");
}
pushToPop();
return stackPop.peek();
}
}
}
两个队列实现栈
public class TwoQueueImplementStack {
public static class TwoQueueStack<T> {
public Queue<T> queue;
public Queue<T> help;
public TwoQueueStack() {
queue = new LinkedList<>();
help = new LinkedList<>();
}
public void push(T value) {
queue.offer(value);
}
public T poll() {
while (queue.size() > 1) {
help.offer(queue.poll());
}
T ans = queue.poll();
Queue<T> tmp = queue;
queue = help;
help = tmp;
return ans;
}
public T peek() {
while (queue.size() > 1) {
help.offer(queue.poll());
}
T ans = queue.peek();
Queue<T> tmp = queue;
queue = help;
help = tmp;
help.offer(ans);
return ans;
}
public boolean isEmpty() {
return queue.isEmpty();
}
}
}
哈希表
是根据关键码值(Key value)而直接进行访问的数据结构,本质是数组,映射函数叫做散列函数,存放记录的数叫做散列表,键值冲突导致得查找问题,叫做哈希冲突。
- 解决哈希冲突得方法:
(1)链地址法
(2)开放地址法
更多精彩关注微信公众号