算法与数据结构全阶班-左程云版(二)基础阶段之2.链表、栈、队列、递归行为、哈希表和有序表(上)

简介: 本文主要介绍了一些常用的数据结构,包括链表、栈、队列、递归、哈希表和有序表。

引言

本文主要介绍了一些常用的数据结构,包括链表、栈、队列、递归、哈希表和有序表。

1.链表结构

单链表节点结构:

class Node {
      public int value;
      public Node next;
      public Node(int data) {
          value = data;
      }
  }

双向链表节点结构:

class DoubleNode {
    public int value;
    public DoubleNode last;
    public DoubleNode next;
    public DoubleNode(int data) {
        value = data;
    }
}

简单练习:

1)单链表和双链表如何反转

2)把给定值都删除


实现如下:

// 反转单链表
public static Node reverseLinkedList(Node head) {
    Node pre = null;
    Node next = null;
    while (null != head) {
        next = head.next;
        head.next = pre;
        pre = head;
        head = next;
    }
    return pre;
}
// 反转双向链表
public static DoubleNode reverseDoubleList(DoubleNode head) {
    DoubleNode pre = null;
    DoubleNode next = null;
    while (null != head) {
        next = head.next;
        head.next = pre;
        head.last = next;
        pre = head;
        head = next;
    }
    return pre;
}
// 删除单链表元素
public static Node removeValue(Node head, int num) {
    // 跳过链表头部值为num的部分
    while (head != null) {
        if (head.value != num) {
            break;
        }
        head = head.next;
    }
    // 头节点为第一个值不为num的节点
    Node pre = head;
    Node cur = head;
    while (cur != null) {
        // pre始终保持其值不等于num
        if (num == cur.value) {
            pre.next = cur.next;
        } else {
            pre = cur;
        }
        cur = cur.next;
    }
    return head;
}
// 删除双向链表元素
public static DoubleNode removeValue(DoubleNode head, int num) {
    // 跳过头节点
    while (head != null) {
        if (head.value != num) {
            break;
        }
        head = head.next;
    }
    if (head != null) {
        head.last = null;
    }
    DoubleNode cur = head, pre = head;
    while (cur != null) {
        if (cur.value == num) {
            pre.next = cur.next;
            cur.last = null;
        } else {
            pre = cur;
        }
        cur = cur.next;
    }
    return head;
}

Java和C++在垃圾回收方面存在区别:


当一块内存空间所对应的变量或引用不存在时,就会自动释放这块内存,Java存在内存泄漏的原因是因为变量的生命周期不同,例如一个生命周期较短的方法中对一个生命周期更长的数据结构进行了操作,但是调用结束时并没有恢复,简单来说,内存空间找不到对应变量或引用就会被释放,否则就不会被释放;


C++ 内存泄漏是因为声明的变量忘记释放,必须手动调用函数释放。

2.栈和队列

栈:数据先进后出,犹如弹匣;

队列:数据先进先出,好似排队。

栈和队列的实现:

(1)基于双向链表

package structure02;
/**
 * @author Corley
 * @date 2021/10/7 14:02
 * @description LeetCodeAlgorithmZuo-structure02
 */
public class LinkedListQueueStack {
    /*
    自定义节点
     */
    static class Node<T> {
        public Node<T> last;
        public Node<T> next;
        public T value;
        public Node(T value) {
            this.value = value;
        }
    }
    /*
    自定义双向链表
     */
    static class DoubleLinkedList<T> {
        public Node<T> head;
        public Node<T> tail;
        public void addFromHead(T value) {
            Node<T> cur = new Node<>(value);
            if (null == head) {
                head = cur;
                tail = cur;
            } else {
                cur.next = head;
                head.last = cur;
                head = cur;
            }
        }
        public void addFrombottom(T value) {
            Node<T> cur = new Node<>(value);
            if (null == head) {
                head = cur;
                tail = null;
            } else {
                cur.last = tail;
                tail.next = cur;
                tail = cur;
            }
        }
        public T popFromHead() {
            if (null == head) {
                return null;
            }
            T res = head.value;
            if (head == tail) {
                head = null;
                tail = null;
            } else {
                head = head.next;
                head.last = null;
            }
            return res;
        }
        public T popFromBottom() {
            if (null == head) {
                return null;
            }
            T res = tail.value;
            if (head == tail) {
                head = null;
                tail = null;
            } else {
                tail = tail.last;
                tail.next = null;
            }
            return res;
        }
        public boolean isEmpty() {
            return null == head;
        }
    }
    /*
    自定义栈
     */
    static class Stack<T> {
        private final DoubleLinkedList<T> stack;
        public Stack() {
            stack = new DoubleLinkedList<>();
        }
        public void push(T value) {
            stack.addFromHead(value);
        }
        public T pop() {
            return stack.popFromHead();
        }
        public boolean isEmpty() {
            return stack.isEmpty();
        }
    }
    /*
    自定义队列
     */
    static class Queue<T> {
        private final DoubleLinkedList<T> queue;
        public Queue() {
            queue = new DoubleLinkedList<>();
        }
        public void push(T value) {
            queue.addFromHead(value);
        }
        public T pop() {
            return queue.popFromBottom();
        }
        public boolean isEmpty() {
            return queue.isEmpty();
        }
    }
}

(2)基于数组

使用数组时,需要考虑数组的大小问题,这里选择使用固定长度的数组来实现。

其中,数组实现较麻烦,如下:

2345_image_file_copy_104.jpg

实现如下:

package structure02;
/**
 * @author Corley
 * @date 2021/10/7 14:50
 * @description LeetCodeAlgorithmZuo-structure02
 * 使用环形数组RingBuffer的思想实现队列
 */
public class ArrayQueueStack {
    static class Queue {
        private final int[] arr;
        private int pushi;          // 加元素的下标
        private int pulli;          // 取元素的下标
        private int size;
        private final int limit;    // 队列大小
        public Queue(int limit) {
            arr = new int[limit];
            pushi = 0;
            pulli = 0;
            size = 0;
            this.limit = limit;
        }
        public void push(int num) {
            if (size == limit) {
                throw new RuntimeException("队列已满,不能再添加元素!");
            }
            size++;
            arr[pushi] = num;
            pushi = nextIndex(pushi);
        }
        public int pull() {
            if (isEmpty()) {
                throw new RuntimeException("队列已空,不能再取元素!");
            }
            size--;
            int res = arr[pulli];
            pulli = nextIndex(pulli);
            return res;
        }
        public boolean isEmpty() {
            return 0 == size;
        }
        private int nextIndex(int index) {
            return index < (limit - 1) ? (index + 1) : 0;
        }
    }
    class Stack {
        int[] arr;
        int size;
        int limit;
        public Stack(int limit) {
            arr = new int[limit];
            this.limit = limit;
            size = 0;
        }
        public void push(int num) {
            if (size == limit) {
                throw new RuntimeException("栈已满,不能再添加元素!");
            }
            arr[size++] = num;
        }
        public int pop() {
            if (0 == size) {
                throw new RuntimeException("栈已空,不能再取元素!");
            }
            return arr[--size];
        }
    }
}

既然语言都提供了这些结构和API,为什么还需要手写代码:


1)算法问题无关语言;

2)语言提供的API是有限的,当有新的功能是API不提供的就需要改写;

3)任何软件工具的底层都是最基本的算法和数据结构,这是绕不过去的。


实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能

1)pop: push、getMin操作的时间复杂度都是O(1);

2)设计的栈类型可以使用现成的栈结构。


实现思路1如下:


维护两个栈,一个栈保存数据,另一个栈保存到当前高度的最小值,如下:


2345_image_file_copy_106.jpg

实现如下:

static class MinStack1 {
    private final Stack<Integer> stackData;
    private final Stack<Integer> stackMin;
    public MinStack1() {
        this.stackData = new Stack<>();
        this.stackMin = new Stack<>();
    }
    public void push(int num) {
        if (this.stackMin.isEmpty()) {
            this.stackMin.push(num);
        } else if (num < this.stackMin.peek()) {
            this.stackMin.push(num);
        } else {
            this.stackMin.push(this.stackMin.peek());
        }
        this.stackData.push(num);
    }
    public int pop() {
        if (this.stackData.isEmpty()) {
            throw new RuntimeException("Your stack is empty!");
        }
        stackMin.pop();
        return stackData.pop();
    }
    public int getMin() {
        if (this.stackData.isEmpty()) {
            throw new RuntimeException("Your stack is empty!");
        }
        return  stackMin.peek();
    }
}

实现思路2如下:


维护两个栈,一个栈保存数据,一个栈保存到当前高度的最小值,但是只有当当前要入栈的数≤之前(栈下面)的最小值时才入最小栈,会节省一些空间,但是会增加时间,因为增加了逻辑判断,如下:

2345_image_file_copy_107.jpg

实现如下:

static class MinStack2 {
    private final Stack<Integer> stackData;
    private final Stack<Integer> stackMin;
    public MinStack2() {
        this.stackData = new Stack<>();
        this.stackMin = new Stack<>();
    }
    public void push(int num) {
        if (this.stackMin.isEmpty()) {
            this.stackMin.push(num);
        } else if (num <= this.stackMin.peek()) {
            this.stackMin.push(num);
        }
        this.stackData.push(num);
    }
    public int pop() {
        if (this.stackData.isEmpty()) {
            throw new RuntimeException("Your stack is empty!");
        }
        int res = stackData.pop();
        if (res == getMin()) {
            stackMin.pop();
        }
        return res;
    }
    public int getMin() {
        if (this.stackData.isEmpty()) {
            throw new RuntimeException("Your stack is empty!");
        }
        return  stackMin.peek();
    }
}

栈和队列的常见面试题:

1)如何用栈结构实现队列结构;

2)如何用队列结构实现栈结构。


用队列实现栈:

用两个队列来实现,包括原始队列和辅助队列,如下:


2345_image_file_copy_108.jpg

两个队列角色互相切换。


实现如下:

static class TwoQueueStack<T> {
    private Queue<T> queue;
    private Queue<T> help;
    public TwoQueueStack() {
        queue = new LinkedList<>();
        help = new LinkedList<>();
    }
    public void push(T value) {
        queue.offer(value);
    }
    public T pop() {
        while (queue.size() > 1) {
            help.offer(queue.poll());
        }
        T res = queue.poll();
        Queue<T> tmp = queue;
        queue = help;
        help = tmp;
        return res;
    }
    public T peek() {
        while (queue.size() > 1) {
            help.offer(queue.poll());
        }
        T res = queue.poll();
        help.offer(res);
        Queue<T> tmp = queue;
        queue = help;
        help = tmp;
        return res;
    }
    public boolean isEmpty() {
        return queue.isEmpty();
    }
}


相关文章
|
2月前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
63 4
|
8天前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
68 5
|
9天前
|
机器学习/深度学习 算法
基于改进遗传优化的BP神经网络金融序列预测算法matlab仿真
本项目基于改进遗传优化的BP神经网络进行金融序列预测,使用MATLAB2022A实现。通过对比BP神经网络、遗传优化BP神经网络及改进遗传优化BP神经网络,展示了三者的误差和预测曲线差异。核心程序结合遗传算法(GA)与BP神经网络,利用GA优化BP网络的初始权重和阈值,提高预测精度。GA通过选择、交叉、变异操作迭代优化,防止局部收敛,增强模型对金融市场复杂性和不确定性的适应能力。
139 80
|
2天前
|
机器学习/深度学习 算法
基于遗传优化的双BP神经网络金融序列预测算法matlab仿真
本项目基于遗传优化的双BP神经网络实现金融序列预测,使用MATLAB2022A进行仿真。算法通过两个初始学习率不同的BP神经网络(e1, e2)协同工作,结合遗传算法优化,提高预测精度。实验展示了三个算法的误差对比结果,验证了该方法的有效性。
|
5天前
|
机器学习/深度学习 数据采集 算法
基于PSO粒子群优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
本项目展示了基于PSO优化的CNN-GRU-SAM网络在时间序列预测中的应用。算法通过卷积层、GRU层、自注意力机制层提取特征,结合粒子群优化提升预测准确性。完整程序运行效果无水印,提供Matlab2022a版本代码,含详细中文注释和操作视频。适用于金融市场、气象预报等领域,有效处理非线性数据,提高预测稳定性和效率。
|
1天前
|
算法
基于梯度流的扩散映射卡尔曼滤波算法的信号预处理matlab仿真
本项目基于梯度流的扩散映射卡尔曼滤波算法(GFDMKF),用于信号预处理的MATLAB仿真。通过设置不同噪声大小,测试滤波效果。核心代码实现数据加载、含噪信号生成、扩散映射构建及DMK滤波器应用,并展示含噪与无噪信号及滤波结果的对比图。GFDMKF结合非线性流形学习与经典卡尔曼滤波,提高对非线性高维信号的滤波和跟踪性能。 **主要步骤:** 1. 加载数据并生成含噪测量值。 2. 使用扩散映射捕捉低维流形结构。 3. 应用DMK滤波器进行状态估计。 4. 绘制不同SNR下的轨迹示例。
|
6天前
|
机器学习/深度学习 算法 索引
单目标问题的烟花优化算法求解matlab仿真,对比PSO和GA
本项目使用FW烟花优化算法求解单目标问题,并在MATLAB2022A中实现仿真,对比PSO和GA的性能。核心代码展示了适应度计算、火花生成及位置约束等关键步骤。最终通过收敛曲线对比三种算法的优化效果。烟花优化算法模拟烟花爆炸过程,探索搜索空间,寻找全局最优解,适用于复杂非线性问题。PSO和GA则分别适合快速收敛和大解空间的问题。参数调整和算法特性分析显示了各自的优势与局限。
|
28天前
|
算法
基于WOA算法的SVDD参数寻优matlab仿真
该程序利用鲸鱼优化算法(WOA)对支持向量数据描述(SVDD)模型的参数进行优化,以提高数据分类的准确性。通过MATLAB2022A实现,展示了不同信噪比(SNR)下模型的分类误差。WOA通过模拟鲸鱼捕食行为,动态调整SVDD参数,如惩罚因子C和核函数参数γ,以寻找最优参数组合,增强模型的鲁棒性和泛化能力。
|
14天前
|
供应链 算法 调度
排队算法的matlab仿真,带GUI界面
该程序使用MATLAB 2022A版本实现排队算法的仿真,并带有GUI界面。程序支持单队列单服务台、单队列多服务台和多队列多服务台三种排队方式。核心函数`func_mms2`通过模拟到达时间和服务时间,计算阻塞率和利用率。排队论研究系统中顾客和服务台的交互行为,广泛应用于通信网络、生产调度和服务行业等领域,旨在优化系统性能,减少等待时间,提高资源利用率。