- 前言
之前整理到使用Java去实现简单的动态数据结构链表,单向链表,其实链表也可以作为其他数据结构的底层实现,来实现更高级的数据结构。今天就整理下通过链表去实现栈,队列,集合和映射这四个数据结构。
- 栈
之前也整理过使用动态数组的方式去实现了一个栈,其实通过链表也可以实现栈这种后进先出的数据结构。
首先声明一个带有虚拟头结点的单向链表,之前博客有贴过,这边就直接上代码。
/**
* 单向链表
* @author Y
* @date 2020/3/27
*/
public class LinkedList<T> {
private class Node {
private Node next;
private T data;
public Node(Node next, T data) {
this.next = next;
this.data = data;
}
public Node(T data) {
this(null,data);
}
public Node() {
}
}
private Node dummyHead; // 虚拟头结点
private int size;
public LinkedList() {
this.dummyHead = new Node();
this.size = 0;
}
public int getSize() {
return this.size;
}
public boolean isEmpty() {
return this.size == 0;
}
public void add(int index, T data) {
if (index < 0 || index > size)
throw new IllegalArgumentException("illegal param");
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
// 找到前一个节点
prev = prev.next;
}
Node node = new Node(data);
node.next = prev.next;
prev.next = node;
size++;
}
public void addFirst(T data) {
add(0,data);
}
public void addLast(T data) {
add(size-1,data);
}
public T delete(int index) {
if (index < 0 || index >= size)
throw new IllegalArgumentException("illegal param");
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
T res = prev.next.data;
prev.next = prev.next.next;
size--;
return res;
}
public T deleteFirst() {
return delete(0);
}
public T deleteLast() {
return delete(size - 1);
}
public boolean contains(T data) {
Node cur = dummyHead.next;
while(cur != null) {
if (cur.data.equals(data))
return true;
cur = cur.next;
}
return false;
}
//删除指定元素
public T deleteNode(T data) {
Node prev = dummyHead;
while (prev.next != null) {
if (prev.next.data.equals(data))
break;
prev = prev.next;
}
if (prev.next != null) {
T res = prev.next.data;
prev.next = prev.next.next;
size--;
return res;
}
return null;
}
// 获得指定下标元素
public T get(int index) {
if (index < 0 || index >= size)
throw new IllegalArgumentException("illegal param");
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.data;
}
public T getFirst() {
return get(0);
}
public T getLast() {
return get(size - 1);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("LinkedList ");
for (Node cur = dummyHead.next; cur != null; cur = cur.next) {
sb.append(cur.data+"->");
}
sb.append("NULL");
return sb.toString();
}
}
使用链表去实现栈,其实链表实现栈非常简单,栈是后进先出的,可以利用链表的性质,压栈和弹栈都在链表的一端进行操作就可以了,这边使用在链表头部进行栈的操作。
首先还是声明栈的接口。
/**
* @author Y
* @date 2020/3/27
*/
public interface Stack<T> {
void push(T t);
T pop();
T peek();
boolean isEmpty();
int getSize();
}
使用链表去实现栈。
/**
* @author Y
* @date 2020/3/27
*/
public class LinkedListStack<T> implements Stack<T> {
private LinkedList<T> list = new LinkedList<>(); // 这边使用链表实现栈
@Override
public void push(T t) {
// 这边选择在链表头进行添加元素操作
list.addFirst(t);
}
@Override
public T pop() {
// 相应删除也在链表头进行
return list.deleteFirst();
}
@Override
public T peek() {
return list.getFirst();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public int getSize() {
return list.getSize();
}
@Override
public String toString() {
return list.toString();
}
}
测试用例和测试结果。
@Test
public void testLinkedListStack() {
LinkedListStack<Integer> linkedListStack = new LinkedListStack<>();
for (int i = 0; i < 15; i++) {
linkedListStack.push(i);
if (i==3 || i==5) {
System.out.println("before pop "+linkedListStack);
System.out.println("pop "+linkedListStack.pop());
System.out.println("after pop "+linkedListStack);
}
}
}
- 队列
队列是先进先出的数据结构,可以在链表的一端添加元素,然后再在另一端取出元素。
首先是队列的接口声明。
/**
* @author Y
* @date 2020/3/27
*/
public interface Queue<T> {
void offer(T t);
T poll();
T peek();
int getSize();
boolean isEmpty();
}
使用链表去实现队列。
/**
* @author Y
* @date 2020/3/27
*/
public class LinkedListQueue<T> implements Queue<T> {
private LinkedList<T> list = new LinkedList<>(); //使用链表实现队列
@Override
public void offer(T t) {
// 在一端添加元素
list.addFirst(t);
}
@Override
public T poll() {
// 在另一端取出元素
return list.deleteLast();
}
@Override
public T peek() {
return list.getLast();
}
@Override
public int getSize() {
return list.getSize();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public String toString() {
return list.toString();
}
}
测试用例和测试结果。
@Test
public void testLinkedListQueue() {
LinkedListQueue<Integer> linkedListQueue = new LinkedListQueue<>();
for (int i = 0; i < 15; i++) {
linkedListQueue.offer(i);
if (i==3 || i==5) {
System.out.println("before poll "+linkedListQueue);
System.out.println("poll "+linkedListQueue.poll());
System.out.println("after poll "+linkedListQueue);
}
}
}
这边可能要考虑时间复杂度的问题,队列和栈不一样,栈都是在链表的头端进行添加和删除操作的,时间复杂度相对比较低,都是o(1),但是队列需要在链表的头和尾都进行操作,所以对应添加和删除的时间复杂度分别为o(1)和o(n),这边可以通过实现带有头结点和尾结点的链表来降低队列操作的时间复杂度。
/**
* @author Y
* @date 2020/3/27
*/
public class LinkedListQueue2<T> implements Queue<T> {
private class Node {
private Node next;
private T data;
public Node(Node next, T data) {
this.next = next;
this.data = data;
}
public Node(T data) {
this(null,data);
}
public Node() {
}
}
private Node head,tail; // 使用首尾结点
private int size;
public LinkedListQueue2() {
this.head = null;
this.tail = null;
this.size = 0;
}
@Override
public void offer(T t) {
// 在一端添加元素
if (tail == null) {
head = tail = new Node(t);
} else {
Node node = new Node(t);
tail.next = node;
tail = node;
}
size++;
}
@Override
public T poll() {
// 在另一端取出元素
if (isEmpty())
return null;
head = head.next;
if (head == null)
tail = null;
size--;
return head.data;
}
@Override
public T peek() {
if (isEmpty())
return null;
return head.data;
}
@Override
public int getSize() {
return this.size;
}
@Override
public boolean isEmpty() {
return this.size == 0;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Queue: front ");
Node cur = head;
while(cur != null) {
res.append(cur + "->");
cur = cur.next;
}
res.append("NULL tail");
return res.toString();
}
public static void main(String[] args) {
LinkedListQueue<Integer> queue = new LinkedListQueue<>();
for(int i = 0 ; i < 10 ; i ++){
queue.offer(i);
System.out.println(queue);
if(i % 3 == 2){
queue.poll();
System.out.println(queue);
}
}
}
}
- 集合
集合是由一个或者多个元素组成的整体,集合中不允许有重复的元素,我们同样可以使用链表这种数据结构去实现集合。
首先是集合的接口定义。
/**
* @author Y
* @date 2020/3/27
*/
public interface Set<T> {
void add(T t);
void delete(T t);
boolean contains(T t);
int getSize();
boolean isEmpty();
}
使用链表实现集合,其实使用链表实现集合也非常的容易,只要在添加元素时增加一层判断就可以了。
/**
* @author Y
* @date 2020/3/27
*/
public class LinkedListSet<T> implements Set<T> {
private LinkedList<T> list = new LinkedList<>();
@Override
public void add(T t) {
// 首先判断下是否有相同元素
if (!list.contains(t))
list.addFirst(t);
}
@Override
public void delete(T t) {
list.deleteNode(t);
}
@Override
public boolean contains(T t) {
return list.contains(t);
}
@Override
public int getSize() {
return list.getSize();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
}
测试用例和测试结果。
@Test
public void testLinkedListSet() {
LinkedListSet<Integer> linkedListSet = new LinkedListSet<>();
int[] array = new int[]{1,2,3,4,5,1,2,3,5,7,9}; // 使用一个有重复数据的数组进行测试
for (int i = 0; i < array.length; i++) {
linkedListSet.add(array[i]);
}
System.out.println(linkedListSet.getSize());
System.out.println(linkedListSet.contains(10));
linkedListSet.delete(9);
System.out.println(linkedListSet.getSize());
}
- 映射
映射是由key和value组成,key不能重复,也叫作字典。
首先还是声明接口。
/**
* 映射 字典
* @author Y
* @date 2020/3/27
*/
public interface Map<K,V> {
V get(K key);
void add(K key, V value);
boolean contains(K key);
void set(K key, V value);
V remove(K key);
int getSize();
boolean isEmpty();
}
映射有两个泛型K V, 我们需要适当修改下我们的链表的结构,使链表也存储两个数值 K 和 V。map对应的实现也都是利用链表实现好的方法加一点修改完成的。
/**
* @author Y
* @date 2020/3/27
*/
public class LinkedListMap<K,V> implements Map<K,V> {
private class Node {
private K key;
private V value;
private Node next;
public Node(K key, V value) {
this.key = key;
this.value = value;
this.next = null;
}
public Node() {
}
}
private Node dummyHead;
private int size;
public LinkedListMap() {
this.dummyHead = new Node();
this.size = 0;
}
// 根据key值获取结点
private Node getNode(K key) {
Node cur = dummyHead.next;
while (cur != null) {
if (key.equals(cur.key)) {
return cur;
}
cur = cur.next;
}
return null;
}
@Override
public V get(K key) {
Node node = getNode(key);
return node == null ? null : node.value;
}
@Override
public void add(K key, V value) {
Node node = getNode(key);
if (node == null) {
Node node1 = new Node(key,value);
node1.next = dummyHead.next;
dummyHead.next = node1;
size++;
} else {
// nothing
node.value = value;
}
}
@Override
public boolean contains(K key) {
return getNode(key) != null;
}
@Override
public void set(K key, V value) {
Node node = getNode(key);
if (node == null)
throw new IllegalArgumentException("key "+key+" is not exist");
node.value = value;
}
@Override
public V remove(K key) {
Node prev = dummyHead;
while (prev.next != null) {
if (prev.next.key.equals(key))
break;
prev = prev.next;
}
if (prev.next != null) {
V res = prev.next.value;
prev.next = prev.next.next;
size--;
return res;
}
return null;
}
@Override
public int getSize() {
return this.size;
}
@Override
public boolean isEmpty() {
return this.size == 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Node cur = dummyHead.next;
sb.append("{");
while (cur != null) {
sb.append(cur.key+"="+cur.value+" ");
cur = cur.next;
}
sb.append("}");
return sb.toString();
}
}
测试用例和测试结果。
@Test
public void testLinkedListMap() {
LinkedListMap<Character,Integer> map = new LinkedListMap<>();
String str = "yuanzhiddddkkkffdwqeefsfdfgsadweqef"; //
char[] chars = str.toCharArray();
for (char c : chars) {
map.add(c,map.get(c) == null ? 1:map.get(c)+1);
}
System.out.println(map);
System.out.println(map.get('s'));
map.remove('s');
System.out.println("after remove s "+map);
map.set('f',1000);
System.out.println("after set f "+map);
}
今天就先整理这么多,自己的数据结构真的差好多~~,希望有什么问题大家可以指出一起交流。