数据结构基础
1、数组
连续空间
2、链表
不连续,随机存储
上述两种都是物理结构
链表是一个容器,节点是其最小的单位,方法针对容器
构造链表LinkList
public class LinkList{
// 用来存储链表的头部
private Node head;
// 用来存储链表的尾部
private Node tail;
public LinkList(){
// 初始化链表
initList();
}
/**
* 初始化链表
*/
public void initList(){
// 创建头节点 链表的第一个节点的引用将保存在head.next中
head = new Node();
head.value = 0;
head.next = null;
// 初始时没有元素 尾节点指向头节点
tail = head;
}
}
增删改查
底层: 节点
记住:
1、)node和node.next是不同变量,不存在指针覆盖!
2、)赋值的时候,注意不要指针覆盖了
如:node.next =s ; s.next = node.next;

这样s覆盖了node.next的指针,导致了s.next指向s,循环指!
且指针覆盖只会前覆盖后!
链表的拷贝是浅拷贝,拷贝对象内属性会影响原始对象(地址.属性),但是指针改变不会影响!(地址 = 地址)
增:
/**
*插入到指定的位置
*需要调用getIndex(index-1) 调取前一个结点
*/
public static void addNodeByIndex(int data,int index) {
Node node = new Node(data);
if (index<0 || index > size){
try {
throw new Exception("索引超出范围");
} catch (Exception e) {
e.printStackTrace();
}
}
//空链表
if (size ==0){
head = node;
last = node;
}else if (index == 0){
//头
node.next = head;
head = node;
}else if( index == size ){
//尾插
last.next = node;
last = node;
}else {
//中间
Node indexNode = getIndex(index-1); //获得index-1的Node
node.next= indexNode.next;
indexNode.next = node ;
}
size++;
}
删
public void deleteByIndex(int index){
if(index<0 || index>size){
try {
throw new Exception("超出索引范围");
} catch (Exception e) {
e.printStackTrace();
}
}if (index == 0){
//头
head = head.next;
}else if(index == size-1){
//尾
Node preNode = getIndex(index -1);
preNode.next = null;
last = preNode;
}else {
//中
Node preNode = getIndex(index -1 );
preNode.next= preNode.next.next;
}
size --;
}
问题:
//问题: temp和last是同一个东西嘛? 是同一东西!
//事实上: 他们的地址相同,但是在改变内部值得时候,发现能相互影响!
查
// 查找
public Node getIndex(int index){
int i =0 ;
Node temp =head; //关键点:临时变量遍历整个链表
while (temp != null && i<index ){
//不超过范围的查找
temp = temp.next;
i++;
}
return temp;
}
3、栈和队列
3.1概念
栈和队列的本质是逻辑结构,可以用1、2实现!
栈: 只操作栈尾 先进后出,简单
队列: 先进先出,循环队列!
3.2栈和队列的实际实现
method1、双向链表实现
1、添加:尾插、头插
2、 弹出: 头弹出、尾弹出
队列: 尾插,头弹 、头插,尾弹
栈: 尾插,尾弹
method2、数组实现
1、栈 (数组 加一个index , index ++ : 入栈 index --: 弹栈 )
2、队列 (循环index)
method1、
队尾和对头是循环的状态
(indexOfLast + 1)% length == indexOfHead 此时队列满了
这样有缺点,容量变成 length-1
method2、
添加size limit,将队尾和队首与空队列满队列解耦
优点:容量能使用完,且好懂[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LVRmPcDa-1610532340636)(C:\Users\huihui520\AppData\Roaming\Typora\typora-user-images\image-20201103163905113.png)]
pollindex:队尾
putindex :队首
size :当前个数
limit :容量
3.3 栈和队列实现
(1)栈用数组实现
//加一个数
public void push(int value) {
if (size == limit) {
throw new RuntimeException("栈满了,不能再加了");
}
size++;
arr[pushi] = value;
pushi = nextIndex(pushi); //来到下一个数的位置
}
public int pop() {
if (size == 0) {
throw new RuntimeException("栈空了,不能再拿了");
}
size--;
int ans = arr[polli];
polli = nextIndex(polli);
return ans;
}
(2)队列用数组实现
//初始化
public static class MyQueue {
private int[] arr;
private int pushi;
private int polli;
private int size;
private final int limit;
public MyQueue(int limit) {
arr = new int[limit];
pushi = 0;
polli = 0;
size = 0;
this.limit = limit;
}
}
(3)如何用栈结构实现队列结构
两个栈
1、数据先导入push
2、push给pop
3、pop就能完成
// pushToPop 原则:1、只有再pop为空时,2、将push全部值都转移进去
// poll 只有当俩栈都为空的时候才报异常,否则需要先pushToPop后再poll
// add 不影响poptopush的情况下 随时都能push ,由于单线程下,不能同时poptopush和push并发,所以随时都可以
伪代码:
结构
//队列内部俩栈
public static class TwoStacksQueue {
public Stack<Integer> stackPush;
public Stack<Integer> stackPop;
public TwoStacksQueue() {
stackPush = new Stack<Integer>();//逆序
stackPop = new Stack<Integer>(); //顺序
}
}
add
public static void add( int value){
stackPush.push(value);
poptopush();
}
poll
public static int poll(){
//空
if(stackPush.empty()&&stackPop.empty()){
throw new RuntimeException("QUEUE is empty");
}
popToPush();
return stackPop.pop();
}
popToPush
public static void popToPush(){
// 1、 2、
if(stackPop.empty){
while(!stackPush.empty){
stackPop.push(stackPush.pop());
}
}
}
(4)用队列结构实现栈结构
俩队列,data,help
注意:压入时机,只能压入data ; 弹出时机只能弹data的最后一个数据
1、装入所有数据 data
2、将data的其他数压入help,队尾当成栈顶pop
3、help 变data,data变help重复23
栈:先进后出,所以12345的顺序应该是54321出来
结构:
public static class TwoQueueStack<T> {
public Queue<T> data;
public Queue<T> help;
public TwoQueueStack() {
data = new LinkedList<>();
help = new LinkedList<>();
}
}
push
public static void push(T value){
data.offer(value);
}
pop
public static T pop(){
//data只剩一个
while(data.size()>1){
help.offer(data.poll());
}
//交换data和help
T ans = data.poll
Queue<T> temp = data;
data = help;
help = temp;
return ans;
}
4、散列表
key-value键值对
hashcode
index = HashCode(key)%Array.length;
JDK7 数组+链表
JKD8 数组+链表+红黑树
1)哈希表在使用层面上可以理解为一种集合结构
2)使用哈希表增(put)、删(remove)、改(put)和查(get)的操作,可以认为时间复杂度为 O(1),但是常数时间比较大
3)放入哈希表的东西,如果是基础类型,内部按值传递,内存占用是这个东西的大小
4)放入哈希表的东西,如果不是基础类型,内部按引用传递,内存占用是8字节
PS (基础类型指的是什么?hash里面 Int 、String是基础类型)
5、有序表TreeMap
//最小
System.out.println(treeMap.firstKey());
//最大
System.out.println(treeMap.lastKey());
// <= 4 的数
System.out.println(treeMap.floorKey(4));
// >= 4 离4最近数
System.out.println(treeMap.ceilingKey(4));
5)有序表把key按照顺序组织起来,而哈希表完全不组织
6)注意时间复杂度为O(logN)
可以自己实现比较器!
5、递归
递归本质: 将大问题转为同样难度的小问题,直到basecase出现
递归过程:将递归的调用图画出来,整个过程就清楚了
上述:递归查找最大值
1、设计好递归函数
1)意义
2)参数
2、寻找basecase 和 向下遍历情况!
6、主定理
时间复杂度
主定理
形如 子问题规模和主问题
T(N) = a * T(N/b) + O(N^d)(其中的a、b、d都是常数)
的递归函数,可以直接通过Master公式来确定时间复杂度
如果 log(b,a) < d,复杂度为O(N^d)
如果 log(b,a) > d,复杂度为O(N^log(b,a))
如果 log(b,a) == d,复杂度为O(N^d * logN)
Ps 这个就是关注 递归和递归中常数时间谁为主,d是递归函数的时间复杂度
a\Roaming\Typora\typora-user-images\image-20201104110545742.png" alt=“image-20201104110545742” style=“zoom: 33%;” />
上述:递归查找最大值
1、设计好递归函数
1)意义
2)参数
2、寻找basecase 和 向下遍历情况!
6、主定理
时间复杂度
主定理
形如 子问题规模和主问题
T(N) = a * T(N/b) + O(N^d)(其中的a、b、d都是常数)
的递归函数,可以直接通过Master公式来确定时间复杂度
如果 log(b,a) < d,复杂度为O(N^d)
如果 log(b,a) > d,复杂度为O(N^log(b,a))
如果 log(b,a) == d,复杂度为O(N^d * logN)
Ps 这个就是关注 递归和递归中常数时间谁为主,d是递归函数的时间复杂度
Ex1 T(n) = 2[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lG4VtKRK-1610532449821)(%28C:%5CUsers%5Chuihui520%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20201116145337724.png#pic_center)]
T(n/2) +theta(1) ----> O(N^log22 )