目录
- 一、动态数组
- 二、链表
- 三、栈
- 四、队列
一、动态数组
整体结构思维导图
-------------------------------------------------------------------------------- 回到目录
完整源码
函数含义:
- add(),在某个下标位置添加元素;
- remove(),移除某个下标对应的值;
- resize(),动态扩容,当数组容量满或者空闲整个数组的3/4时,重新定义容量;
- set(),设置某个下标的元素值;
- get(),获取某个位置的元素值;
- rangeCheck(),检查数组下标是否越界;
- contains(),查找是否包含某个元素;
- find(),找到某个元素的下标;
public class Array<E> {
private E[] data;
private int size; //元素个数
//构造函数
public Array(int capacity){
data = (E[])new Object[capacity]; //注意如何new泛型数组
size = 0;
}
public Array(){
this(10);
}
//增
public void add(int index, E e){
if(size == data.length)
resize(2 * data.length);
if(index < 0 || index > size)
throw new IllegalArgumentException("add full");
for(int i = size - 1; i >= index; i --){
data[i + 1] = data[i];
}
data[index] = e;
size ++;
}
public void addFirst(E e){
add(0, e);
}
public void addLast(E e){
add(size, e);
}
//删
public E remove(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException("illegal");
E e;
e = data[index];
for(int i = index ; i < size ; i ++){
data[i] = data[i + 1];
}
size --;
data[size] = null; // 垃圾回收。可省略。loitering objects != memory leak
if(size == data.length / 4 && data.length / 2 != 0) // 到了1/4时才进行缩容
resize(data.length / 2);
return e;
}
public E removeFirst(){
return remove(0);
}
public E removeLast(){
return remove(size - 1);
}
public void removeElement(E e){
int index = find(e);
if(index != -1)
remove(index);
}
//扩容/缩容函数,在增和删里使用
private void resize(int newCapacity){
E[] newData = (E[])new Object[newCapacity];
for(int i = 0 ;i < size ; i++){
newData[i] = data[i];
}
data = newData;
}
//改
public void set(int index, E e){
if(index < 0 || index >= size)
throw new IllegalArgumentException("illegal");
data[index] = e;
}
//查
public E get(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException("illegal");
return data[index];
}
public int getSize(){
return size;
}
public int getCapacity(){
return data.length;
}
public boolean isEmpty(){
return size == 0;
}
public void rangeCheck(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException("Index is Illegal!");
}
public boolean contains(E e){
for(int i = 0 ; i < size ; i ++){
if(data[i].equals(e))
return true;
}
return false;
}
public int find(E e){
for(int i = 0 ; i < size ; i ++){
if(data[i] == e)
return i;
}
return -1;
}
//打印函数
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("size = %d , capacity = %d\n", size, data.length ));
res.append("[");
for(int i = 0 ; i < size ; i ++){
res.append(data[i]);
if(i < size - 1)
res.append(",");
}
res.append("]");
return res.toString();
}
}
-------------------------------------------------------------------------------- 回到目录
二、链表
关于虚拟头结点
在向链表添加元素的过程中我们遇到了个问题,就是在链表头添加元素和在链表的其他地方添加元素逻辑上会有差别,为什么会有差别?
答:因为在添加元素的过程中,我们总要找到待添加元素节点的前一个节点。但是添加第一个节点(头结点)的时候没有前一个节点,所以在添加第一个节点时逻辑就会特殊一些。
因此为了统一操作,方便作业,给头节点造一个虚拟头节点,里面不包含任何元素。
虚拟头结点习惯性命名为dummyHead。这是约定俗成问题,大家把这个空节点叫做真正的头节点。
整体结构思维导图
-------------------------------------------------------------------------------- 回到目录
1、单链表
增加节点的操作顺序图示:
删除节点的操作顺序图示:
完整源码
public class SingleList<E> {
//内部类
private class Node{
public E val;
public Node next;
public Node(E val, Node next) {
this.val = val;
this.next = next;
}
public Node(E val) {
this(val,null);
}
public Node(){
this(null,null);
}
@Override
public String toString() {
return val.toString();
}
}
private Node dummyHead; //虚拟的头结点
private int size; //表示链表当前的元素
//构造函数
public SingleList() {
dummyHead = new Node();
size = 0;
}
//增
public void add(int index,E e){
if(index < 0 || index > size){
throw new IllegalArgumentException("Index is Illegal ! ");
}
Node prev = dummyHead;
for(int i = 0; i < index; i++)
prev = prev.next;
Node node = new Node(e);
node.next = prev.next;
prev.next = node;
size++;
}
public void addFirst(E e){
add(0,e);
}
public void addLast(E e){
add(size,e);
}
//删
public E remove(int index){
rangeCheck(index);
Node prev = dummyHead;
for(int i = 0; i < index; i++)
prev = prev.next;
Node delNode = prev.next;
prev.next = delNode.next;
delNode.next = null;
size--;
return delNode.val;
}
public E removeFirst(){
return remove(0);
}
public E removeLast(){
return remove(size - 1);
}
public void removeElement(E e){
Node prev = dummyHead;
while(prev.next != null){ //找到对应e的prev
if(prev.next.val.equals(e)){
break;
}
prev = prev.next;
}
if(prev.next != null){
Node delNode = prev.next;
prev.next = delNode.next;
delNode.next = null;
size--;
}
}
//改
public void set(int index,E e){
rangeCheck(index);
Node cur = dummyHead.next;
for(int i = 0; i < index; i++)
cur = cur.next;
cur.val = e;
}
//查
public E get(int index) {
rangeCheck(index);
Node cur = dummyHead.next;
for (int i = 0; i < index; i++)
cur = cur.next;
return cur.val;
}
public E getFirst(){
return get(0);
}
public E getLast(){
return get(size - 1);
}
public int getSize(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
public void rangeCheck(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException("Index is Illegal!");
}
public boolean contains(E e){
Node cur = dummyHead.next;
while(cur != null){
if(cur.val.equals(e)){
return true;
}
cur = cur.next;
}
return false;
}
//打印函数
@Override
public String toString(){
StringBuilder res = new StringBuilder();
Node cur = dummyHead.next;
while(cur != null){
res.append(cur + "->");
cur = cur.next;
}
res.append("NULL");
return res.toString();
}
}
-------------------------------------------------------------------------------- 回到目录
2、双向链表
增加节点的操作顺序图示:
删除节点的操作顺序图示:
完整源码
public class DoubleList<E> {
//内部类
private class Node{
public E val;
public Node prev;
public Node next;
public Node(E val, Node prev, Node next) {
this.val = val;
this.prev = prev;
this.next = next;
}
public Node(E val){
this(val,null,null);
}
public Node(){
this(null,null,null);
}
@Override
public String toString() {
return val.toString();
}
}
private Node dummyHead;
private int size;
//构造函数
public DoubleList() {
dummyHead = new Node();
size = 0;
}
//增
public void add(int index,E e){
if(index < 0 || index > size){
throw new IllegalArgumentException("Index is Illegal ! ");
}
Node pre = dummyHead;
for(int i = 0; i < index; i++)
pre = pre.next;
Node node = new Node(e);
Node nxt = pre.next;
//操作顺序如图示
node.prev = pre;
node.next = (nxt == null) ? null : nxt;
pre.next = node;
if(nxt != null)
nxt.prev = node;
size++;
}
public void addFirst(E e){
add(0,e);
}
public void addLast(E e){
add(size,e);
}
//删
public E remove(int index){
rangeCheck(index);
if(isEmpty())
return null;
Node pre = dummyHead;
for(int i = 0; i < index; i++)
pre = pre.next;
Node delNode = pre.next;
pre.next = delNode.next;
if(delNode.next != null)
delNode.next.prev = pre; //!!!
size--;
return delNode.val;
}
public E removeFirst(){
return remove(0);
}
public E removeLast(){
return remove(size-1);
}
//查
public int getSize(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
public void rangeCheck(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException("Index is Illegal!");
}
//打印函数
@Override
public String toString() {
StringBuilder res = new StringBuilder();
Node cur = dummyHead.next;
while(cur != null){
res.append(cur + "->");
cur = cur.next;
}
res.append("NULL");
return res.toString();
}
}
-------------------------------------------------------------------------------- 回到目录
3、双向循环链表
双向循环链表图示:
增加节点的操作顺序图示:
删除节点的操作顺序图示:
注意:
- 插入的时候要注意如果是在最后插入的话,可以直接通过dummyHead(虚拟头结点找到最后的),然后直接插入;
- 在get的时候,可以判断一下从哪边开始查找,加快查找速度;
完整源码
public class MyLinkedList<E>{
//内部类
private class Node{
public E e;
public Node prev;
public Node next;
public Node(E e, Node prev, Node next) {
this.e = e;
this.prev = prev;
this.next = next;
}
public Node(E e){
this(e,null,null);
}
public Node (){
this(null,null,null);
}
@Override
public String toString() {
return e.toString();
}
}
private Node dummyHead;
private Node tail;
private int size;
//构造函数
public MyLinkedList() {
dummyHead = new Node();
tail = dummyHead;
//dummyHead自成一环
dummyHead.next = tail;
dummyHead.prev = tail;
tail.next = dummyHead;
tail.prev = dummyHead;
size = 0;
}
//增
//add Last
public void add(E e){
//操作顺序如图示
Node node = new Node(e);
node.prev = tail;
node.next = dummyHead;
tail.next = node;
dummyHead.prev = node;
tail = node;
size++;
}
public void add(int index, E e){
if(index < 0 || index > size){
throw new IllegalArgumentException("Index is Illegal ! ");
}
if(index == size){ //add to Last
add(e);
return;
}
Node pre = dummyHead;
for(int i = 0; i < index; i++)
pre = pre.next;
Node nxt = pre.next;
Node node = new Node(e);
//操作顺序如图示
node.prev = pre;
node.next = nxt;
pre.next = node;
nxt.prev = node;
size++;
}
//删
public E remove(int index){
rangeCheck(index);
if(index == size - 1){
return removeLast();
}
Node pre = dummyHead;
for(int i = 0; i < index; i++)
pre = pre.next;
Node delNode = pre.next;
pre.next = delNode.next;
delNode.next.prev = delNode.prev;
size--;
return delNode.e;
}
public E removeLast(){
E ret = tail.e;
tail.prev.next = tail.next;
tail.next.prev = tail.prev;
tail = tail.prev; //改变tail
size--;
return ret;
}
//查
public E get(int index){
rangeCheck(index);
Node cur = dummyHead;
if(index < (size << 1)){
for(int i = 0; i < index + 1; i++)
cur = cur.next;
return cur.e;
}
else {
for(int i = 0; i < index + 1; i++)
cur = cur.prev;
return cur.e;
}
}
public int getSize(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
public void rangeCheck(int index){
if(index < 0 || index >= size){
throw new IllegalArgumentException("Index is Illegal ! ");
}
}
//打印函数
@Override
public String toString() {
StringBuilder res = new StringBuilder();
Node cur = dummyHead.next;
while(cur != dummyHead){
res.append(cur.e + "->");
cur = cur.next;
}
res.append("NULL");
return res.toString();
}
}
-------------------------------------------------------------------------------- 回到目录
三、栈
接口:
public interface Stack<E> {
void push(E e); //将元素压入栈顶
E pop(); //移除栈顶元素
int getSize(); //查看栈中元素的长度
boolean isEmpty(); //判断栈顶是否为空
E peek(); //查看栈顶元素
}
1、基于动态数组实现的栈
除 Array 中的方法外,额外在 Stack 工程中的 Array 类里添加两个方法:
public E getLast(){
return get(size - 1);
}
public E getFirst(){
return get(0);
}
完整源码
public class ArrayStack<E> implements Stack<E> {
Array<E> array;
public ArrayStack(int capacity){
array = new Array<>(capacity);
}
public ArrayStack(){
array = new Array<>();
}
@Override
public int getSize(){
return array.getSize();
}
@Override
public boolean isEmpty(){
return array.isEmpty();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public void push(E e){
array.addLast(e);
}
@Override
public E pop(){
return (E) array.removeLast();
}
@Override
public E peek(){
return (E) array.getLast();
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("Stack: [");
for(int i = 0 ; i < array.getSize() ; i++){
res.append(array.get(i));
if(i != array.getSize() - 1 )
res.append(",");
}
res.append("] top");
return res.toString();
}
}
-------------------------------------------------------------------------------- 回到目录
2、基于链表实现的栈
完整源码
public class LinkedListStack<E> implements Stack<E>{
private SingleList<E> list;
public LinkedListStack(){
list = new LinkedList<>();
}
@Override
public int getSize(){
return list.getSize();
}
@Override
public boolean isEmpty(){
return list.isEmpty();
}
@Override
public void push(E e){
list.addFirst(e);
}
@Override
public E pop(){
return list.removeFirst();
}
@Override
public E peek(){
return list.getFirst();
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("Stack :");
res.append(list);
res.append("love you baby");
return res.toString();
}
}
-------------------------------------------------------------------------------- 回到目录
测试二者的时间复杂度:
import java.util.Random;
public class Main {
public static double testStack(Stack<Integer> stack , int opCount){
long startTime = System.nanoTime();
Random random = new Random();
for(int i = 0 ; i < opCount ; i ++)
stack.push(random.nextInt(Integer.MAX_VALUE));
for(int i = 0 ; i < opCount ; i ++)
stack.pop();
long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
}
public static void main(String[] args){
ArrayStack<Integer> arr = new ArrayStack<>();
LinkedListStack<Integer> lin = new LinkedListStack<>();
int time = 10000000;
double tt = testStack(arr , time);
System.out.println("arr = " + tt);
double tw = testStack(lin , time);
System.out.println("lin = " + tw);
}
}
-------------------------------------------------------------------------------- 回到目录
四、队列
接口:
public interface Queue<E> {
void enqueue(E e); //元素进入队列
E dequeue(); //元素退出队列
E getFront(); //查看队头的第一个值
int getSize(); //获取队列长度
boolean isEmpty(); //判断队列是否为空
}
1、基于动态数组实现的队列
除 Array 中的方法外,额外在 Queue 工程中的 Array 类里添加一个方法:
public E getFirst(){
return get(0);
}
完整源码
public class ArrayQueue<E> implements Queue<E>{
//我们要用自己写的逻辑套进Queue的函数里面,所以我们要建一个变量array来实现queue应该做的事。
//除了本身带有的函数外,别忘了写两个构造函数、一个查看队列中最多可装多少元素的函数getCapacity和一个打印toString函数
//重写接口里的的函数时记得写 @Override
private Array<E> array;
public ArrayQueue(int capacity){
array = new Array<>(capacity);
}
public ArrayQueue(){
array = new Array<>();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public void enqueue(E e){
array.addLast(e);
}
@Override
public int getSize(){
return array.getSize();
}
@Override
public boolean isEmpty(){
return array.isEmpty();
}
@Override
public E dequeue(){
return array.removeFirst();
}
@Override
public E getFront(){
return array.getFirst( );
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("Queue:"));
res.append("f [");
for(int i = 0 ; i < array.getSize() ; i ++){
res.append(array.get(i));
if(i < array.getSize() - 1)
res.append(",");
}
res.append("] t");
return res.toString();
}
}
-------------------------------------------------------------------------------- 回到目录
2、基于链表实现的队列
注意:
- 为了dequeue的方便,增加了一个tail指针指向尾节点;
- 注意队列为空的情况要特判
完整源码
public class LinkedListQueue<E> implements Queue<E> {
private class Node {
public E e;
public Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e){
this(e, null);
}
public Node(){
//this.e = null;
// this.next = null;
this(null, null);
}
@Override
public String toString(){
return e.toString();
}
}
private Node head, tail;
private int size;
public LinkedListQueue(){
head = null;
tail = null;
size = 0;
}
@Override
public int getSize(){
return size;
}
@Override
public boolean isEmpty(){
return size == 0;
}
@Override
public void enqueue(E e){
if(tail == null){
tail = new Node(e);
head = tail;
}
else{
tail.next = new Node(e);
tail = tail.next;
}
size ++;
}
@Override
public E dequeue(){
if(isEmpty())
throw new IllegalArgumentException("fail");
Node res = head;
head = head.next;
res.next = null;
if(head == null)
tail = null;
size --;
return res.e;
}
@Override
public E getFront(){
if(isEmpty())
throw new IllegalArgumentException("fail");
return head.e;
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("Queue:");
Node cur = head;
while(cur != null){
res.append(cur + "->");
cur = cur.next;
}
return res.toString();
}
}
-------------------------------------------------------------------------------- 回到目录
3、循环队列
注意:
- 循环队列的内置数组中要浪费一个空间用来区分队列空和满;
- 循环队列的出队操作比ArrayQueue要快很多,原因在于ArrayQueue出队要移动大量元素;
完整源码
public class LoopQueue<E> implements Queue<E>{
private E[] data;//一个数组,即一个抽象上的容器,用来装数据形成队列的
private int front;
private int tail;
private int size;
public LoopQueue(int capacity){
data = (E[])new Object[capacity + 1];
front = 0;
tail = 0;
size = 0;
}
public LoopQueue(){
this(10);
}
@Override
public int getSize(){ //查看现在的队列中一共有几个元素
return size;
}
public int getCapacity(){
return data.length - 1; //整个容器长度 - 1 为真正队列的长度
}
@Override
public boolean isEmpty(){
return front == tail;
}
//扩容/缩容函数
private void resize(int newCapacity){
E[] newData = (E[])new Object[newCapacity + 1];
for(int i = 0 ; i < size ; i ++){
newData[i] = data[(i + front) % data.length];
}
data = newData;
front = 0;
tail = size;
}
@Override
public void enqueue(E e){
if((tail + 1) % data.length == front) //判断队列是否是满的,满的话则扩容
resize(getCapacity() * 2);
data[tail] = e;
size ++;
tail = (tail + 1) % data.length;
}
@Override
public E dequeue(){
E ret = data[front];
if(isEmpty())
throw new IllegalArgumentException("fail");
data[front] = null;
front = (front + 1) % data.length;
size --;
if(size == getCapacity() / 4 && getCapacity() / 2 != 0)
resize(getCapacity() / 2);
return ret;
}
@Override
public E getFront(){
if(isEmpty())
throw new IllegalArgumentException("fail");
return data[front];
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("Queue: size = %d , capacity = %d", size, getCapacity()));
res.append("f [");
for(int i = front; i != tail; i = (i + 1) % data.length){
res.append(data[i]);
if((i + 1) % data.length != tail)
res.append(",");
}
res.append("] t");
return res.toString();
}
}
-------------------------------------------------------------------------------- 回到目录
比较数组队列和循环队列的运行时间:
import java.util.Random;
public class time {
//private double test = 100000;
private static double testQueue(Queue<Integer> q, int opCount){
long start = System.nanoTime();
Random random = new Random();
for(int i = 0; i < opCount; i++)
q.enqueue(random.nextInt(Integer.MAX_VALUE));
for(int i = 0; i < opCount ; i++)
q.dequeue();
long end = System.nanoTime();
//double end;
return (end - start) / 1000000000.0;
}
public static void main(String[] args){
int opCount = 100000;
ArrayQueue<Integer> arr = new ArrayQueue<>();
double t1 = testQueue(arr, opCount);
System.out.println(t1);
LoopQueue<Integer> loo = new LoopQueue<>();
double t2 = testQueue(loo, opCount);
System.out.println(t2);
}
}
-------------------------------------------------------------------------------- 回到目录