在Java研发工程师招聘中,数据结构与算法是必考的题目,不久看到一篇文章《为什么面试总喜欢考算法题》提到:面试考算法是一个基准点,因为算法是计算机学科中最基础的学科。本着不死也脱层皮的想法就买了本算法书开始啃,虽然其中很多题目我就是想破脑汁也想出来,可我居然能沉浸在这样的状态中自得其乐。“算法虐我千百遍,我仍待它如初恋”。
顺序存储结构
顺序存储结构数据存在内存地址连续的一块区域,数组便是这种存储结构的典型表现。由于是在一块的内存区域,只要确定第一个元素的内存地址,后面的元素就可以在这快区域上随机存取。下面的示意图展示了顺序存储结构在内存中的存储形式:
数组实现顺序存储结构
使用数组实现顺序存储结构的主要操作有:查找数据、插入数据和删除数据,其中插入数据和删除数据是本算法中更为核心的部分,因为在数组中要要插入一个数据,必须把数据往后面移动,而且还要考虑是否会出现数组下标越界的情况。删除操作也是一样,要从一个数组中删除一个数据不是不是一件易事(至少从计算机层面来讲是这样的),每次删除一个数据需要把数据往前面移动。下面请看核心代码实现:
public class ListArray implements List {
//数组容量
private int len = 8;
//数组中元素的个数
private int size;
//数组之间元素的比较策略
private Strategy strategy;
//用于装载数据元素的数组
private Object[] elements;
public ListArray(){
this(new DefaultStrategy());
}
public ListArray(Strategy strategy) {
this.strategy = strategy;
size = 0;
elements = new Object[len];
}
//获取第i位置上元素
public Object get(int i) {
if(i < 0 || i > size){
System.out.println("下标越界,获取失败!");
return null;
}
return elements[i];
}
//在第i个位置插入数据元素e
public boolean insert(int i, Object e) throws OutOfBoundException {
if(i < 0 || i > size){
System.out.println("下标越界,插入失败!");
return false;
}else{
//如果数组中元素的大小超过了数组的容量,则增大数组的容量
if(size >= elements.length){
expandSpace();
}
//把从第i位置的元素开始到最后一个元素,都往后移动一个位置
for(int j = size - 1; j > i; j--){
elements[j] = elements[j-1];
}
//把第i位置的值改为e
elements[i] = e;
//把数组元素的格式增加1
size++;
return true;
}
}
//动态扩展数组容量
private void expandSpace() {
Object[] newArray = new Object[elements.length*2];
for (int i = 0; i < elements.length; i++) {
newArray[i] = elements[i];
}
elements = newArray;
}
//移除数组中第i位置的元素并返回
public Object remove(int i) throws OutOfBoundException {
Object e = get(i);
if(i < 0 || i > size){
System.out.println("下标越界,删除失败!");
return null;
}
for (int j = i; j < size - 1; j++) {
elements[j] = elements[j + 1];
}
//把数组的元素个数减少1
elements[--size] = null;
return e;
}
//删除元素e,并判断是否删除成功
public boolean remove(Object e) {
boolean isRemove = false;
//判断数组中是否存在元素e
if(contains(e)){
//获取该元素的下标
int i = indexOf(e);
for (int j = i; j < size - 1; j++) {
elements[j] = elements[j + 1];
}
isRemove = true;
}
return isRemove;
}
//定位元素e的位置
public int indexOf(Object e) {
for (int i = 0; i < size; i++) {
if(strategy.equal(elements[i], e)){
return i;
}
}
return -1;
}
}
链式存储结构
链式存储结构区别于顺序存储结构的一点就是数据是存放内存中任意一个地址中,每个数据除了有自己的数据属性还有下一个数据内存地址的引用,所以得到一个数据就可以知道下一个数据在哪个位置,显然这种存储方式的最有利的一点就是删除和插入比较有效率,这也是链式存储结构的应用的优势,在Java中,有一种数据结构————链表,就很好体现了这种存储方式,下面就用链表来实现链式存储结构
链表实现链式存储结构
前面提到,链表的一大优势就是删除和插入比较给力,为了便于理解下面的代码,我截了两张图说明链表删除以及插入的具体过程:
需要注意的是,执行链表插入的时候要先判断插入位置的下标防止下标越界的情况,这个时候可以抛出异常来解决,下面来看链表删除的具体过程:
下面是具体的核心代码实现:
public class LinkedSTList implements List {
//单链表数据元素比较策略
private Strategy strategy;
//头结点
private STNode head;
//单链表中元素的个数
private int size;
public LinkedSTList(){
this(new DefaultStrategy());
}
public LinkedSTList(Strategy strategy){
this.strategy = strategy;
head = new STNode();
size = 0;
}
//定位元素e在链表中的下标
public int indexOf(Object e) {
STNode p = head.getNext();
int index = 0;
while(p != null){
if(strategy.equal(p.getData(), e)){
return index;
}else{
index++;
p = p.getNext();
}
}
return -1;
}
//在单链表的第i位置插入元素e
public boolean insert(int i, Object e) throws Exception {
STNode s = new STNode(e,null);
if(i <= 0 || i >= size){
throw new OutOfBoundException("下标越界,插入失败");
}
STNode p = getPre(i);
STNode self = (STNode) get(i);
p.setNext(s);
s.setNext(self);
size++;
return true;
}
//获取第i位置的前一个节点
private STNode getPre(int i) throws OutOfBoundException {
if(i < 0 || i >= size){
throw new OutOfBoundException("下标越界,插入失败");
}
/*if(i == 0){
return head;
}else{
STNode p = head.getNext();
int index = 1;
while(i != index){
p = p.getNext();
index++;
}
return p;
}*/
STNode p = head;
for(;i > 0; i--){
p = p.getNext();
}
return p;
}
//获取元素e的前一个元素节点
private STNode getPre(Object e) {
STNode p = head;
while(p != null){
if(strategy.equal(p.getNext().getData(), e)){
return p;
}else{
p = p.getNext();
}
}
return null;
}
//删除第i位置的元素并返回
public Object remove(int i) throws Exception {
if(i < 0 || i >=size){
throw new OutOfBoundException("下标越界,删除失败");
}
/*STNode s = (STNode) get(i);
if(i == 0){
head = head.getNext();
size--;
}else if(i == size-1){
STNode p = getPre(i);
p.setNext(p.getNext().getNext());
size--;
}else{
STNode q = getPre(i);
q.setNext(s.getNext());
size--;
}*/
STNode p = getPre(i);
Object obj = p.getNext().getData();
p.setNext(p.getNext().getNext());
size--;
return obj;
}
public boolean remove(Object e) {
if(contains(e)){
STNode s = getPre(e);
s.setNext(s.getNext().getNext());
size--;
return true;
}
return false;
}
}