仅作为学习数据结构与算法分析边通过微博记录,蓝色为知识点。
上一篇我们已经了解了基本的算法,并能做出合理的耗时分析,本片主要讲解的点是集中抽象数据类型(ADT).
1.数组(这个太简单了直接跳过了)能手写一个ArrayList即可。
2.链表(手写一个LinkedList)
3.栈(手写一个后缀运算)
4.队列(这个应用太多了讲下概念,之后着重讲图)
一。数组和链表(贴手写LinkedList代码,这个总体还是有点意思)
数组这块我只讲一下ArrayList的实现理念,实际代码不贴了太长了。
JDK的List是继承Collection<E>
public interface List<E> extends Collection<E> {
public interface Collection<E> extends Iterable<E> {
迭代器方法在Collection里面被定义
Iterator<E> iterator();
所以手写ArrayListDemo直接实现Interable即可
新增修改删除,还有iterator都需要手动实现。
这里我主要讲一下LikedList
LikedList是链表,且是双向链表,双向是因为链性结构头节点(header node)和尾节点(tail node)都是特殊的存在,因为删除时候需要找到指出最后节点的项把它变成null,删除第一个的时候需要删除第二个指出的上一个节点。
为了解决这个问题,LinkedList特殊声明了header node和tail node
这里画个图给大家示意一下:
上图为LinkedList的数据结构,按位置查找链表的速度低于数组即数组为O(N)链表为O(N^2),但是使用迭代器一次next的话速度同为O(N).按位置新增和删除链表的速度为O(N),而数组为O(N^2)。
这里我主要给大家讲解一个LinkedList新增和修改的逻辑,了解了新增,删除的逻辑自然也通了。
这里我直接就贴上我自己写的代码,内容都在备注里边,注:迭代器部分我给省略了
代码下载地址:
package com.shen.my_j_u;
import java.util.Iterator;
public class MyLinkedList <AnyType> implements Iterable<AnyType>{
//链表长度
private int theSize;
//链表操作计数器,当清空,新增,删除时自增
private int modCount = 0 ;
//特殊节点:头节点
private Node<AnyType> beginMarker;
//特殊节点:尾节点
private Node<AnyType> endMarker;
public MyLinkedList(){
doClear();
}
@Override
public Iterator<AnyType> iterator() {
return null;
}
/**
* 节点对象
* @param <AnyType>
*/
private static class Node<AnyType>{
/**
* 节点构造器
* @param d 节点数据对象
* @param p 当前节点前节点
* @param n 当前节点后节点
*/
public Node(AnyType d,Node<AnyType> p,Node<AnyType> n){
data = d;
prev = p;
next = n;
}
public AnyType data;
public Node<AnyType> prev;
public Node<AnyType> next;
}
/**
* 链表初始化方法
*/
private void doClear(){
beginMarker = new Node<>(null,null,null);
endMarker = new Node<>(null,beginMarker,null);
beginMarker.next = endMarker;
theSize = 0;
modCount++;
}
/**
* 获取当前链表长度
* @return
*/
public int size(){
return theSize;
}
//插入对象方法,默认位置插入链表尾部
public boolean add(AnyType x){
add(size(),x);
return true;
}
//按位置插入对象方法
public boolean add(int idx,AnyType x){
addBefore(getNode(idx,0,size()),x);
return true;
}
//按位置修改方法
public AnyType set(int idx,AnyType newVal){
Node<AnyType> p = getNode(idx);
AnyType oldVal = p.data;
p.data = newVal;
return oldVal;
}
/**
* 按位置取出对象
* @param idx 取出对象位置
* @return
*/
private Node<AnyType> getNode(int idx){
return getNode(idx,0,size()-1);
}
/**
* 获取插入位置之前的对象
* @param idx 插入位置
* @param lower 0
* @param upper 现有长度
* @return 插入位置现有元素
*/
private Node<AnyType> getNode(int idx,int lower,int upper){
Node<AnyType> p;
//如果插入位置小于0,或者大于链表现有长度,抛出异常
if(idx<lower || idx >upper)
throw new IndexOutOfBoundsException();
//如果插入位置在链表的前半部分
if(idx<size()/2){
//先赋值头节点,因为当插入位置为第一位时直接弹出
p = beginMarker.next;
//从头节点开始寻找插入位置
for (int i = 0;i<idx;i++)
//取出循环位置的下一个,即为需要插入的位置
p = p.next;
}else {
//当插入位置在链表后半部
//先赋值尾节点,因为当插入位置为最后一位位时直接弹出
p =endMarker;
//从尾位置查询
for (int i= size();i>idx;i--)
//取出循环位置的上一个,即为需要插入的位置
p = p.prev;
}
return p;
}
/**
* 新增节点方法
* @param p 插入位置现有对象数据
* @param x 新增对象数据
*/
private void addBefore(Node<AnyType> p,AnyType x){
//创建新增对象节点,p.prev为之前位置节点的母节点,子节点为插入前前当前位置节点
Node<AnyType> newNode = new Node<>(x,p.prev,p);
//修改父节点子节点,新创建对象节点复制给,之前父节点的自阶段
newNode.prev.next = newNode;
//修改位置节点的父节点
p.prev = newNode;
//长度加1
theSize++;
//操作计数加1
modCount++;
}
}
第二-栈
说到栈大家需要记忆的点:1.push进栈
2.pop出栈
3.top顶部
栈也叫LIFO表,就是先进先出,这里记忆的时候注意他和队列相反即可。
懒了这里就不贴图了。
讲一下栈的应用:栈的应用就比较多了,比如说平衡符号(类似编译器符号检错),后缀计算法(这块树的部分还会讲)
着重讲一下--方法调用
当调用一个新的方法时,主调例程的所有局部变量需要由系统储存起来,否则呗调用的新方法将会重新由主调例程的变量所使用的内存。主调例程的当前位子也需要储存。这时候由一个新的栈来完成,而这正式在实现递归的每一种设计语句中实际发生的事实。这些所储存的信息或曾为活动记录,或叫栈帧