基础算法第二章(java)

数据结构

1、链表与邻接表

用数组模拟链表,因为传统链表每次添加元素都要new一个节点,如果数据在百万级的话,单单new节点都会超时,所以我们需要使用数组模拟链表。

1、单链表:邻接表:存储图和树

2、双链表:优化某些问题

单链表数组实现:

package 数据结构;

// head表示头结点的下标
// e[i]表示节点i的值
// ne[i]表示节点i的next指针是多少
public class ListButArray {
    int N = 10000;

    //head是头指针,指向下一个节点,idx为第一个可以用的节点。
    int head;
    int idx;
    int[] e = new int[N];
    int[] ne = new int[N];

    //初始化
    public ListButArray(){
        head = -1;
        idx = 0;
    }

    public void addFirst(int x){
        //将x赋给当前可以用的节点,x的next指针指向head指针,head的下一个节点为idx,更新idx。
        e[idx] = x;
        ne[idx] = head;
        head = idx;
        idx++;
    }

    //将x插入到下标是k的后面
    public void add(int x , int k){
        e[idx] = x;
        ne[idx] = ne[k];
        ne[k] = idx;
        idx ++;
    }

    //将下标是k的点的后面的点删掉
    public void remove(int k){
        ne[k] = ne[ne[k]];
    }
}

双链表数组实现:

package 数据结构;

public class DListButArray {
   int N = 100010;
   int m;
   int[] e = new int[N];
   int[] l = new int[N];
   int[] r = new int[N];
   int idx;

   public DListButArray(){
      // 0表示左端点,1表示右端点
      r[0] = 1;
      l[1] = 0;
      idx = 2;
   }

   //在下标是k的点的右边,插入x
   public void add(int k , int x){
      e[idx] = x;
      r[idx] = r[k];
      l[idx] = k;
      l[r[k]] = idx;
      r[k] = idx;
   }

   public void remove(int k){
      r[l[k]] = r[k];
      l[r[k]] = l[k];
   }
}

2、栈与队列

栈:

package 数据结构;

public class Stk {
    int N = 1000010;

    int[] stk = new int[N];
    int tt;

    //插入
    public void push(int value){
        stk[++tt] = value;
    }

    //弹出
    public int pop(){
        return stk[tt--];
    }

    //判断栈是否为空
    public boolean isEmpty(){
        return tt > 0;
    }

    //栈顶元素
    public int peek(){
        return stk[tt];
    }
}

单调栈:

给定一个序列,求某个元素左边离它最近的比它大/小的数

队列:

package 数据结构;
//队列
public class queue {
    // 在队尾插入元素,在队头弹出元素
    int N = 1000010;
    int[] q = new int[N];
    int hh = 0;
    int tt = 0;

    //插入
    public void add(int value){
        q[tt++] = value;
    }

    //弹出
    public void removeFirst(){
        hh++;
    }

    //判断队列是否为空
    public boolean isEmpty(){
        return hh >= tt;
    }

    //取出队头
    public int peek(){
        return q[hh];
    }
}

3、kmp

1、暴力算法怎么做

2、如何去优化

求next数组:

for (int i = 2, j = 0; i <= n ; i++) {
            while (j > 0  && p[i] != p[j+1])  j = next[j];
            if (p[i] == p[j+1]) j++;
            next[i] = j;
        }

遍历原串寻找模板串:

for (int i = 1, j = 0; i <= m ; i++) {
            while (j > 0 && s[i] != p[j+1]) j = next[j];
            if (s[i] == p[j+1]) j++;
            if (j == n) {
                sb.append(i - n).append(" ");
                j = next[j];
            }
        }
import java.io.*;

public class Main {
    public static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
    public static PrintWriter pt = new PrintWriter(new OutputStreamWriter(System.out));
    public static final int N = 1000010;
    public static int[] next = new int[N];
    public static char[] p = new char[N];
    public static char[] s = new char[N];
    public static void main(String[] args) throws IOException {
        StringBuilder sb = new StringBuilder();
        int n = Integer.parseInt(bf.readLine());
        String ps = bf.readLine();;
        int m = Integer.parseInt(bf.readLine());
        String ss = bf.readLine();
        for (int i = 0; i < ps.length(); i++) {
            p[i+1] = ps.charAt(i);
        }
        for (int i = 0; i < ss.length(); i++) {
            s[i+1] = ss.charAt(i);
        }
        for (int i = 2, j = 0; i <= n ; i++) {
            while (j > 0  && p[i] != p[j+1])  j = next[j];
            if (p[i] == p[j+1]) j++;
            next[i] = j;
        }

        for (int i = 1, j = 0; i <= m ; i++) {
            while (j > 0 && s[i] != p[j+1]) j = next[j];
            if (s[i] == p[j+1]) j++;
            if (j == n) {
                sb.append(i - n).append(" ");
                j = next[j];
            }
        }
        pt.println(sb);
        bf.close();
        pt.flush();
        pt.close();
    }

}

4、Trie字典树

用来高效的存储和查找字符串集合的数据结构

import java.util.*;
public class Main{
    static int N = 100010;
    static int[][] son = new int[N][26];//节点数组 , 一共有N个节点
    static int[] cnt = new int[N];//表示当前节点存储了多少个字符串
    static int idx = 0;//数组的初始下标,节点从这个下标开始存储
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int x = sc.nextInt();
        for(int i = 0;i<x;i++){
            String c = sc.next();
            String s = sc.next();
            if("I".equals(c)) insert(s.toCharArray());
            if("Q".equals(c)) System.out.println(query(s.toCharArray()));
        }
    }
    public static void insert(char[] str){
        int p = 0;
        for(int i = 0;i<str.length;i++){
            int u = str[i] - 'a';
            if(son[p][u] == 0) son[p][u] = ++idx; //开辟新节点
            p = son[p][u]; //寻找下一个节点
        }
        cnt[p] ++ ; //该字符串计数加一
    }
    public static int query(char[] str){
        int p = 0;
        for(int i = 0;i<str.length;i++){
            int u = str[i] - 'a';
            if(son[p][u] == 0) return 0;
            p = son[p][u];
        }
        return cnt[p];
    }
}

5、并查集

1、将两个集合合并

2、询问两个元素是否在一个集合中

基本原理:每个集合用一棵树来表示。树根的编号就是整个集合的编号。每个节点存储它的父节点,p[x]表示x的父节点

问题1:如何判断树根:if(p[x] == x)

问题2:如何求x的集合编号:while(p[x]!=x) x = p[x];

问题3:如何合并两个集合:px是x的集合编号,py是y的集合编号。p[x] = p[y]

路径压缩优化:将路径上的所有节点直接指向根节点

import java.util.*;
public class Main{
    static int N = 100010;
    static int[] p = new int[N];
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        for(int i = 0;i<n;i++){
            p[i] = i;
        }
        while(m-->0){
            String s = sc.next();
            int a = sc.nextInt();
            int b = sc.nextInt();
            if("M".equals(s)){
                p[find(a)] = find(b);
            }
            if("Q".equals(s)){
                if(find(a) == find(b)) System.out.println("Yes");
                else System.out.println("No");
            }
        }
        p = new int[N];
    }
    public static int find(int x){
        if(p[x]!=x) p[x] = find(p[x]);
        return p[x];
    }
}

6、如何手写一个堆?

1、插入一个数 heap[++size] = x; up(size);

2、求集合当中的最小值 heap[1];

3、删除最小值 heap[1] = heap[size]; size–; down(1);

4、删除任意一个元素 heap[k] = heap[size]; size–; down(k); up(k);

5、修改任意一个元素 heap[k] = x; down(k); up(k);

import java.util.*;
public class Main{
    static int N = 100010;
    static int[] h = new int[N];
    static int size = 0;
    static int[] ph = new int[N];
    static int[] hp = new int[N];
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = 0;
        while(n--!=0){
            String s =sc.next();
            if("I".equals(s)){
                int x = sc.nextInt();
                size++;
                m++;
                ph[m] = size;
                hp[size] = m;
                h[size] = x;
                up(size);
            }else if("PM".equals(s)){
                System.out.println(h[1]);
            }else if("DM".equals(s)){
                heap_swap(1 , size);
                size--;
                down(1);
            }else if("D".equals(s)){
                int k = sc.nextInt();
                k = ph[k];
                heap_swap(k , size);
                size--;
                down(k);
                up(k);
            }else if("C".equals(s)){
                int k = sc.nextInt();
                int x = sc.nextInt();
                k = ph[k];
                h[k] = x;
                down(k);
                up(k);
            }
        }
    }
    public static void heap_swap(int a , int b){
        int tmp = ph[hp[a]];
        ph[hp[a]] = ph[hp[b]];
        ph[hp[b]] = tmp;
        tmp = hp[a];
        hp[a] = hp[b];
        hp[b] = tmp;
        swap(a , b);
    }
    public static void swap(int a , int b){
        int tmp = h[a];
        h[a] = h[b];
        h[b] = tmp;
    }
    public static void down(int u){
        int t = u;
        if(u * 2 <= size && h[u * 2] < h[t]) t = u*2;
        if(u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
        if(u != t){
            heap_swap(u , t);
            down(t);
        }
    }
    public static void up(int u){
        while(u / 2 > 0 && h[u / 2] > h[u]){
            heap_swap(u / 2 , u);
            u/=2;
        }
    }
}

如何计算堆数组中的父子节点映射关系?

下标从0开始:

父节点:(p - 1) / 2

左节点:2 * p +1 右节点:2 * p + 2

下标从1开始:

父节点:p / 2

左节点:2 * p 右节点:2 * p + 1

总结

数据结构这一章不是特别的难,主要是怎么优化已有的API或者怎么对基础的数据结构变形,使之能够应用到对应的题目上。例如食物链就需要维护一个节点到根节点的距离,用来表示当前节点对应的类别。或者是堆排序,需要维护节点的插入顺序和节点所在的位置(这题饶了我好久)。总之,算法是死的,人是活(死)的,灵活变通,这才是算法。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值