02数据结构基础
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,循环指!
且指针覆盖只会前覆盖后!
链表的拷贝是浅拷贝,拷贝对象内属性会影响原始对象(地址.属性),但是指针改变不会影响!(地址 = 地址),还有
cur = head 时 cur.next和head.next是同一属性
cur .next = new node cur = cur.next 时,head.next.next = cur.next 有传递性!!
所以当想保留head,又想向下遍历的时候,就可以用cur
增:
/**
*插入到指定的位置
*需要调用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 = last.next;
}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-D3Kt827v-1616252175958)
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是递归函数的时间复杂度
Ex1 T(n) = 2T(n/2) +theta(1) ----> O(N^log22 )
7、位运算
待补充,
//位运算优先级特低
int M = L + ((R-L)>>1); //注意括号
8、对数器
可以产生随机输入,且可以规定输入长度、大小等,方便与暴力方法做对照
下面是产生随机int[]
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// for BuildTree
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// for BuildTree
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// for BuildTree
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
同理 double[] 之类的都可以