java实现几种简单的数据结构
备注:本文内容参考学习自阿发老师(网易云:阿发你好)的数据结构与算法课程。
何为数据结构?何为算法?
算法可以理解为我们解决、实现问题用到的数学逻辑、方法,而数据结构则是数据组织结构。数据结构是为算法服务的。
算法的应用场景:
- 操作系统
- 服务器
- 图片视频处理
- 大数据
- 人工智能领域
常见的数据结构
常见的数据结构有数组、队列、链表、栈、哈希表等。
数组
数组是最简单的一种数据结构。
int[] array1 = new int[20]; //动态初始化 0
Integer[] array2 = new Integer[20]; //动态初始化 null
int[] array3 = {1,2,3,4}; //静态初始化--默认初始化长度为4
创建数组有多种办法,初始化的数组必须指定数组的长度。初始化的数组如果没有赋值的话注意其是有默认值的。
链表
链表(LinkedList)也是一种常见的数据结构,样式如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eAb0Kqkj-1592479268325)(C:\Users\xyz\AppData\Roaming\Typora\typora-user-images\image-20200616154515982.png)]
链表中的每个节点(node)都存在一个next指针指向下一个节点,呈现一个链状结构。
普通有头链表的实现 java自带(ArrayList 、 LinkedList)
public class XyzList
{
public static class Node{
//静态内部类,与外部类无关
public Object value;
public Node next;
}
//定义一个头节点/有头链表
public Node head = new Node(); //实例化XyZList时自动加入到实例化的对象中 ===emmm...相当于XyzList内的属性
public void add(Object value)
{
Node tail = head;
while(tail.next != null)
tail = tail.next;
//在尾部添加一个新节点
Node node = new Node();
node.value = value;
tail.next = node;
}
public void remove(Object value)
{
Node node = head;
while(node.next != null)
{
if(node.next.value.equals(value))
{
//value相等则移除该节点
node.next = node.next.next;
}
node = node.next;
}
}
public int size()
{
int count = 0;
Node node = head;
while(node.next != null)
{
count ++;
node = node.next;
}
return count;
}
}
双向链表 java.util.LinkedList
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ncGhUl8H-1592479268327)(C:\Users\xyz\AppData\Roaming\Typora\typora-user-images\image-20200616175459180.png)]
添加节点
public class XyzDoubleList
{
public static class Node{
public Object value;
public Node prev;
public Node next;
}
//头节点
public Node head = new Node(); //field
//尾节点
public Node tail = new Node();//field
public XyzDoubleList()
{
//初始化
head.next = tail;
tail.prev = head;
}
public void add(Object value)
{
// 附加到末尾
Node node = new Node ();
node.value = value;
// 插在尾节点之前
tail.prev.next = node;
node.prev = tail.prev;
node.next = tail;
tail.prev = node;
}
public void remove(Object value)
{
Node node = head.next;
while(node != tail)
{
if(node.value.equals(value)) // 用equals方法进行比较
{
node.prev.next = node.next;
node.next.prev = node.prev;
}
node = node.next;
}
}
public int size()
{
int count = 0;
Node node = head;
while(node.next != tail)
{
count++;
node = node.next;
}
return count;
}
}
循环链表
public class XyzCircularList
{
public static class Node{
public Object value;
public Node prev;
public Node next;
}
//采用无头链表
public Node head = null;
public Node tail = null;
public void add(Object value )
{
Node node = new Node();
node.value = value;
if(head == null)
{
head = node;
tail = node;
node.prev = node.next = node;
}else {
//最好画图理解一下 /此时的添加节点是在末尾添加
tail.next = node;
node.prev = tail;
node.next = head;
head.prev = node;
tail = node;
}
}
}
//////////////////////////////////////////////////////////////////////////////
//适用的一种算法--报数,只留下最后一个没有报到指定数字的人
XyzCircularList list = new XyzCircularList();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
list.add(8);
list.add(9);
list.add(10);
Node node = list.head;
int count = 0;
while(node.next != node)
{
count ++;
if(count == 3)
{
count = 0;
node.prev.next = node.next;
node.next.prev = node.prev;
if(node == list.head)
{
list.head = node.next;
}
}else{}
node = node.next;
}
System.out.println(list.head.value);
}
队列
FIFO ,先进先出,只能从尾部添加数据,头部取出
队列实现:指定一个头节点(也可以实现无头列表,即头部也存放数据),同普通链表(尾部添加数据,头部取出)。
public class XyzQueue
{
public static class Node{
public Object value;
public Node next;
}
private Node head = new Node(); //head = null 无头列表,下面方法需要修改
private int count = 0;
public void add(Object value)
{
Node tail = head;
while(tail.next != null)
tail = tail.next;
Node node = new Node();
node.value = value;
tail.next = node;
count += 1 ;
}
public Object poll()
{
if(head.next == null) return null;
Node node = head.next;
head.next = node.next;
count -= 1;
return node.value;
}
public int size()
{
return count;
}
}
栈
FILO,先进后出
栈可以用链表和数组实现,但是,一般的栈都是指定空间的,用数组实现可以最大程度的减少
开销。
public class XyzStack
{
private Object[] array;
public XyzStack(int size)
{
array = new Object[size];
}
private int index = 0; //记录当前索引
public void add(Object value)
{
array[index] = value;
index += 1;
}
public Object poll()
{
if(index == 0) return -1;
Object value = array[index-1];
array[index-1] = null;
index -= 1;
return value;
}
public Object top()
{
if(index == 0) return -1;
return array[index-1];
}
//查看大小
public int size()
{
return index;
}
//查看容量
public int capacity()
{
return array.length;
}
HashMap
hashmap是一种映射关系的数据结构
HashMap的实现
1 hashmap原理
hash表是key-value形式,并且通过key值可以直接取出对应的value,我们知道数组结构与其有类似之处:我们可以通过数组的下标,直接取出对应的值。那么,我们在存储的时候,直接将key值转化为一个下标,将数据存入数组。在取出的时候,又通过key值得到下标,便可以直接将值取出。
好了,那么我们哈希表的雏形就有了如下
public class XyzHashMap
{
Object[] array = new Object[10000];
public void put(String key,Object value)
{
int index = key2index(key);
array[index] = value;
}
public Object get(String key)
{
int index = key2index(key);
Object value = array[index];
return value;
}
//哈希函数
public int key2index(String key)
{
int index = Integer.valueOf(key);
return index;
}
好了,我们现在的哈希表可以按键值对存在,但存在一个问题,就是key值仅仅只能为“0”、“1”、“2”、…“10000”; 所以接下来我们考虑如何将传入的其它类型key值传化为整形下标以产生对应关系,不难想到,只需要修改上面的哈希函数就可以达到这一目的了。
public int key2index(Object key)
{
//将传入的key值首字母
String name = (String) key;
char first = name.charAt(0);
int index = first;
return index % 10000;// 取余,因为我们现在设计的数组容量只有10000
}
好了,这样我们的key值就既可以是整形如1、2、3 ;也可以是字符串型如“wo”、“ai”、“你”、亦可以:“你好”、“我是”、“钟汉良”等等。那么在继续考虑这样的情况,如有两个不同的key值“wo”、“wow”,在我们这种方法转化后下标相同(位置冲突),这样后面传入的值就会覆盖前面的。那么,如何继续改进呢?
回到前面介绍的链表,假如,我们把数组中每一个下标索引都设置为一个链表头(node),在放入数据的时候将数据挂到相应的索引下面、在查找时,先找到对应的索引,再从该索引下,按照链表查找的方法就可以取出特定key值的数据了。(这种方法就被称为挂链法)
public class XyzHashMap
{
public Node[] array = new Node[10000];
public static class Node
{
public Object key;
public Object value;
public Node next;
}
public XyzHashMap() {
//初始化将每个下标设置为链表头
for(int i =0;i<10000; i++)
{
array[i] = new Node();
}
}
public void put(Object key,Object value)
{
int index = key2index(key);
Node node = new Node();
node.key = key;
node.value = value;
// 将节点挂在链表上
// TODO: 此处最好检查一个是否重复 ,如果已经存在则覆盖之
Node tail = array[index];
while(tail.next != null)
{
if(tail.next.key.equals(key))
{
node.next = tail.next.next;
tail.next = node; //进行替换
return;
}
else {
tail = tail.next;
}
}
node.next = array[index].next;
array[index].next = node;
}
public Object get(Object key)
{
// 将Key值转成数组下标
int index = key2index(key);
// 找到对应的链表, 遍历查找
Node node = array[index].next;
while(node != null)
{
if(node.key.equals(key))
{
return node.value;
}
node = node.next;
}
return null;
}
// 删除一项
public void remove(Object key)
{
// 先找到对应的链表,再从链表中移除 ( 注意这里面是有头链表)
int index = key2index(key);
Node node = array[index];
while(node.next != null)
{
if(node.next.key.equals(key))
{
node.next = node.next.next;
}else
{
node = node.next;
}
}
}
public int key2index(Object key)
{
if(key instanceof Integer)
{
return (Integer)key % 10000;
}
//将传入的key值首字母
String name = (String) key;
char first = name.charAt(0);
int index = first;
return index % 10000;
}
这样我们的简单哈希表就实现了。
在java1.8后的版本中,HashMap是通过hashcode()的方法得到索引,这种方法在存入大量数据的时候,会将数据较为平均的分配到每一个索引下。此外,当挂链的数据超过一定数量时,链表便会转化为红黑树,加快查找速度。
算法
函数栈 ,Call Stack
堆栈 Heap,Stack
递归
递归是最为常见的算法之一,什么是递归呢?“一层一层一层的剥开我的心,你会鼻酸你会流泪”,
将工具手(工具人)类比函数、方法,洋葱(那女孩的心)类比求解的问题,我们想要看看洋葱(心)的最深处是什么(她在想什么呀)? 所以呀,你开始尝试,一层一层的剥开洋葱(去慢慢探索走到她的内心最深处),终于,你剥开了洋葱,也看到了她的心思。可是,它们刺激的你心酸,你有不得不一层一层的合上它。。终于,从外界看来,什么都没有变,但你的确得到的最终的结果,无论喜不喜欢。
递归利用的栈的原理,总是调用一个函数,但传入不同的参数,一层一层一层嵌套下去,当它遇到了一个终止条件(心酸了),又会将函数结果一层一层一层从里至外的返回,也就得到最终的结果了。
常见的问题:斐波那契数列、汉诺塔等经典问题
斐波那契数列(Fibonacci sequence)1、1、2、3、5、8、13、21、34
F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)
public static int count(int number)
{
if(number == 1) return 1;
if(number == 2) return 1;
return count(number -1) + count(number -2) ;
}
汉诺塔
a,b,c ,n层
初始: function = Hnot(n,a,b,c)
step1:a->b,n-1, c为过渡层 function = Hnot(n-1,a,c,b)
step2:a->c,第n层,直接移动过去 moveTo(number,a,c)
step3:b->c,n-1,a为过渡层 function = Hnot(n-1,b,a,c)
终止条件,n=0,return
,**初始A,B,C ,n-为塔数。
public static void move(int number,String from ,String to)
{
count ++ ;
System.out.println(number + "号盘子" + from + "->" + to);
}
public static void hanNuoTa(int n, String a ,String b ,String c)
{
if(n == 0)return;
hanNuoTa(n-1,a,c,b);
move(n,a,c);
hanNuoTa(n-1,b,a,c);
}
初始: function = Hnot(n,a,b,c)
step1:a->b,n-1, c为过渡层 function = Hnot(n-1,a,c,b)
step2:a->c,第n层,直接移动过去 moveTo(number,a,c)
step3:b->c,n-1,a为过渡层 function = Hnot(n-1,b,a,c)
终止条件,n=0,return
,**初始A,B,C ,n-为塔数。
public static void move(int number,String from ,String to)
{
count ++ ;
System.out.println(number + "号盘子" + from + "->" + to);
}
public static void hanNuoTa(int n, String a ,String b ,String c)
{
if(n == 0)return;
hanNuoTa(n-1,a,c,b);
move(n,a,c);
hanNuoTa(n-1,b,a,c);
}