xhao 2020-01-04
栈又名堆栈,是允许在同一端进行插入和删除操作的特殊线性表。其中,允许进行插入和删除操作的一端叫做栈顶(Top),另一端叫做栈底,栈底固定,栈顶浮动。
栈是后进先出的线性表,数据结构如下:
实现一个栈,需要实现以下核心方法:
(1)push():向栈中压入一个数据,先入栈的数据放在最下面。
(2)pop():移除栈顶数据。
(3)peek():返回当前的栈顶数据。
栈的具体实现过程:
package com.jzq.structure.Stack; public class Stack<E> { private Object[] data = null; private int maxSize = 0;//栈的容量 private int top = -1;//栈顶指针 Stack() { this(10);//默认的栈大小为10 } Stack(int initialSize) { if (initialSize >= 0) { this.maxSize = initialSize; data = new Object[initialSize]; top = -1; }else { throw new RuntimeException("初始化大小不能小于0: "+initialSize); } } //进栈,向栈顶压入一个数据,第一个元素top=0 public boolean push(E e){ if (top == maxSize -1){ throw new RuntimeException("栈已满,无法将元素入栈;"); }else { data[++top] = e;//先分配栈空间,再放入元素 return true; } } //出栈,移除栈顶第一个元素 public E pop(){ if (top == -1){ throw new RuntimeException("栈为空"); }else { return (E)data[top--]; } } //查看栈顶元素 public E peek(){ if (top == -1){ throw new RuntimeException("栈为空"); }else { return (E)data[top]; } } }
队列是一种只允许在表的前端进行删除操作且在表的后端进行插入操作的线性表。其中执行插入操作(入队)的端叫做队尾,执行删除操作(出队)的端叫做队头。
队列是先进先出的线性表,具体结构如图:
实现一个队列,需要实现以下核心方法:
(1)add():向队尾加入一个元素。
(2)pop():删除队列头部元素。
(3)peek():取出队列头部元素。
队列的具体实现过程:
package com.jzq.structure.Queue; public class Queue<E> { private Object[] data = null; private int maxSize;//队列长度 private int front;//队列头 private int rear;//队列尾 public Queue() { this(10); } public Queue(int initialSize) { if (initialSize >= 0) { this.maxSize = initialSize; data = new Object[initialSize]; front = rear = 0; } else { throw new RuntimeException("初始化大小不能小于0: " + initialSize); } } public boolean empty() { return maxSize == 0; } //在队尾插入数据 public boolean add(E e) { if (rear == maxSize) { throw new RuntimeException("队列已满,无法插入新的元素"); } else { data[rear++] = e; return true; } } //删除队列头部数据 public E poll() { if (empty()) { throw new RuntimeException("空队列异常"); } else { E value = (E) data[front]; data[front++] = null; return value; } } //查看队列头部数据 public E peek() { if (empty()) { System.out.println("asd"); throw new RuntimeException("空队列异常"); } else { return (E) data[front]; } } }
链表是由一系列节点组成的数据结构,节点可以在运行过程中动态生成。每个节点都包括两个部分内容:存储数据的数据域和存储下一个节点的指针域。
链表有3种不同类型:单向链表、双向链表和循环链表。
单向链表的特点是链表的链接方向是单向的。访问链表时要从头部开始顺序读取。
具体结构如图:
单向链表,需要实现以下操作:
(1)查找:单向链表只可向一个方向遍历,一般从第一个节点开始。
(2)插入:将插入的节点设置为头节点,将Next指针指向原来的头节点即可。
(3)删除:将要删除节点的上一个节点的Next指针指向该节点的下一个节点,然后删除该节点即可。
单向链表的具体实现过程:
package com.jzq.structure.LinkedList.SingleLinkedList; public class SingleLinkedList { private int length; private Node head; public SingleLinkedList() { length = 0; head = null; } private class Node { private Object data; private Node next; public Node(Object data) { this.data = data; } } //插入新节点 public Object addHead(Object obj) { Node newhead = new Node(obj); if (length == 0) { head = newhead; } else { head = newhead; newhead.next = head; } length++; return obj; } //删除单向链表数据 public boolean delete(Object value) { if (length == 0) { return false; } Node current = head; Node previous = head; while (current.data == value) { if (current.next == null) { return false; } else { previous = current; current = current.next; } } if (current == head) { head = current.next; } else { previous.next = current.next; } length--; return true; } //单向链表查询 public Node find(Object obj) { Node current = head; int tempSize = length; System.out.println(length); while (tempSize > 0) { if (obj.equals(current.data)) { System.out.println(current.data); return current; } else { current = current.next; } tempSize--; } return null; } }
双向链表的每个数据都有两个指针,分别指向其直接后继节点和直接前驱节点。
双向链表数据结构如图所示:
双向链表的具体实现过程:
package com.jzq.structure.LinkedList.TwoWayLinkedList; public class TwoWayLinkedList { private Node head;//表示链表头 private Node tail;//表示链表尾 private int length;//表示链表长度 private class Node { private Object data; private Node next; private Node prev; public Node(Object data) { this.data = data; } } public TwoWayLinkedList() { length = 0; head = null; tail = null; } //在链表头部增加节点 public void addHead(Object value) { Node newNode = new Node(value); if (length == 0) { head = newNode; tail = newNode; length++; } else { head.prev = newNode; newNode.next = head; head = newNode; length++; } } //在链表尾部增加节点 public void addTail(Object value) { Node newNode = new Node(value); if (length == 0) { head = newNode; tail = newNode; length++; } else { tail.next = newNode; newNode.prev = tail; tail = newNode; length++; } } //删除链表头部节点 public boolean deleteHead() { if (length == 0) { return false; } if (head.next == null) {//表明链表中有一个节点 head = null; tail = null; } else { head = head.next; } length--; return true; } //删除链表尾部节点 public boolean deleteTail() { if (length == 0) { return false; } if (tail.prev == null) {//表明链表中有一个节点 head = null; tail = null; } else { tail = tail.prev; } length--; return true; } }
循环列表中的最后一个节点的指针域指向头节点,整个链表形成一个环。循环列表与单向链表十分相似。
具体数据结构如图:
由于与单向链表相似,只展示循环链表部分代码:
public CircleLinkedList() { length = 0; head.next = head; }
散列表,也称哈希表,是根据键值对(Key-Value)对数据进行存取的数据结构。
散列表通过映射函数把键值对映射到表中的一个位置来加快查找。
具体数据结构如图:
(1)直接定址法:取k或者k的某个线性函数为Hash值。
(2)平方取值法:取关键字平方后的中间几位为散列地址。
(3)折叠法:将关键字分割成位数相同的几部分,然后取这几部分的叠加和作为散列地址。
(4)除留取余法:取关键字被某个不大于Hash表长度m的数p除后所得的余数为Hash地址 。
(5)随机数法:选择一个随机函数,取关键字的随机函数值作为Hash地址。
散列冲突是指由关键字得到的哈希地址的位置上已存有记录,则“处理冲突”就是为该关键字的记录找到另一个“空”的哈希地址。
解决散列冲突的方法有:开放寻址法(Open Addressing)和链表法(Chaining),以及再哈希法和公共溢出法。