面试算法系列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,循环指!

且指针覆盖只会前覆盖后!

链表的拷贝是浅拷贝,拷贝对象内属性会影响原始对象(地址.属性),但是指针改变不会影响!(地址 = 地址)
在这里插入图片描述

增:

/**
 *插入到指定的位置
 *需要调用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并发,所以随时都可以
image-20201104100045864

伪代码:

结构

//队列内部俩栈	
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

image-20201104095834406

栈:先进后出,所以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出现

递归过程:将递归的调用图画出来,整个过程就清楚了

image-20201104110545742

上述:递归查找最大值

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 )

猜你喜欢

转载自blog.csdn.net/huihui5210/article/details/112580094
今日推荐