数据结构:第一章

视频链接:https://www.bilibili.com/video/BV1HQ4y1d7th

视频范围P1 - P41

本栏主要将数据结构和算法分为十六类:

  1. 数据结构和算法概述
  2. 链表
  3. 稀疏数组和队列
  4. 递归
  5. 排序算法
  6. 查询算法
  7. 哈希表
  8. 树结构应用
  9. 多路查找树
  10. 常用10种算法

1.数据结构和算法概述

  1. 数据结构是计算机存储、组织数据的方式,是指相互之间存在一种或多种特定关系的数据元素的集合
  2. 程序 = 数据结构 + 算法
  3. 数据结构和算法相辅相成

数据结构分为:线性结构、非线性结构

结构性质代表
线性结构数据元素之间存在一对一的线性关系数组、队列、链表、栈
非线性结构二维数组、多维数组、广义表、树结构、图结构

线性结构有两种不同的存储结构:顺序存储结构(数组)和链式存储结构(链表)

存储结构特点
顺序存储结构(数组)顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的
链式存储结构(链表)链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素结点存放数据元素以及相邻元素的地址信息

2.栈

  1. 栈是限制插入和删除只能在一个位置上进行的线性表。
  2. 栈顶(top):允许插入和删除的一端位于表的末端
  3. 栈底(bottom):不允许插入和删除的另一端
  4. 对栈的基本操作有PUSH(压栈)和POP(出栈/弹栈)
  5. PUSH(压栈):相当于表的插入操作(想栈顶插入一个元素)
  6. POP(出栈/弹栈):删除操作(删除一个栈顶元素)
  7. 栈是一种后进先出(LIFO)的数据结构,最先被删除的是最近压栈的元素
  8. 栈实现:由于栈是一个表,因此任何实现表的方法都可以用来实现栈。主要有两种方式,链表实现和数组实现。
  9. 使用链表方式实现的栈又叫动态栈。动态栈有链表的部分特性,即元素与元素之间在物理存储上可以不连续,但是功能有些受限制,动态栈只能在栈顶处进行插入和删除操作,不能在栈尾或栈中间进行插入和删除操作。
  10. 使用数组实现栈叫做静态栈。

2.1 用数组实现栈

数组栈

package stackPractice;

public class ArrayStack {

    //栈的大小
    private int maxStack;

    //定义数组来模拟栈
    private int[] stack;

    //表示栈顶所在的位置,默认情况下如果没有数据,使用-1
    private int top = -1;

    //构造方法:给出栈的具体大小
    public ArrayStack(int maxStack) {
        this.maxStack = maxStack;
        //初始化当前数组的容量
        stack = new int[maxStack];
    }

    //判断是否已经满栈
    public boolean isFull(){
        return this.top == this.maxStack - 1;
    }

    //判断当前栈是否为空栈
    public boolean isEmpty(){
        return this.top == -1;
    }

    //压栈
    public void push(int val){
        //是否已经栈满
        if (isFull()){
            throw new RuntimeException("此栈已满!");
        }

        top++;
        stack[top] = val;
    }

    //出栈,弹栈
    public int pop(){
        //如果栈中是空
        if (isEmpty()){
            throw new RuntimeException("空栈,未找到数据!");
        }

        int value = stack[top];
        top--;

        return value;
    }

    //查看栈中所有元素
    public void list(){
        //是否是空栈
        if (isEmpty()){
            throw new RuntimeException("空栈,未找到数据!");
        }

        for (int i = 0; i < stack.length; i++) {
            System.out.printf("stack[%d] = %d\n",i,stack[i]);
        }
    }

    //栈中元素存在的个数
    public int length(){
        return this.top + 1;
    }
}

2.2 判断回文

回文:一个数据从左往右和从右往左都是一样的 例如:aba;racecar
需求:通过上面以数组模拟栈来判断一个字符串是否是一个回文数据

package stackPractice;

public class stackPracticeTest01 {
    public static void main(String[] args) {
        System.out.println(detecation("hello"));//输出为:false
        System.out.println(detecation("aba"));//输出为:true

    }

    public static boolean detecation(String val){

        //初始化栈对象
        ArrayStack arrayStack = new ArrayStack(10);

        //获取字符串长度
        int length = val.length();

        //把字符串数据逐次获取字符压栈至数组中
        for (int i = 0; i < length; i++) {
            arrayStack.push(val.charAt(i));
        }

        //获取
        String newVal = "";
        int length1 = arrayStack.length();//需要将该代码从for循环中提出来,因为随着top的移动length()值也会改变
        for (int i = 0; i < length1; i++) {
            //是否是一个空栈
            if (!arrayStack.isEmpty()){
                char pop = (char)arrayStack.pop();//弹出的是数字,强转为char类型
                newVal = newVal + pop;
            }
        }

        if (val.equals(newVal)){
            return true;
        }
        return false;
    }
}

2.3 面试题:使用栈完成一个表达式计算结果

需求:计算 String val = "4 + 3 + 2 + 1 * 5"的结果

  1. 循环遍历出来每一个元素
  2. 如果是数字则压入数字栈,如果是一个运算符则压入运算符栈
  3. 如果是运算符:
    如果运算符栈中是空,则直接入栈
    如果运算符栈中不为空,先对比栈中运算符的优先级:
    ①如果优先级小于等于栈中的运算符,则需要把原运算符弹出,数字栈中数字进行弹栈,进行运算,将得到的结果重新放入数字栈中,再把新运算符入运算符栈
    ②如果优先级大于原来的运算符栈中运算符,直接将运算符入栈即可,最终获取一个表达式结果

package stackPractice;

public class ArrayStack {

    //栈的大小
    private int maxStack;

    //定义数组来模拟栈
    private int[] stack;

    //表示栈顶所在的位置,默认情况下如果没有数据,使用-1
    private int top = -1;

    //构造方法:给出栈的具体大小
    public ArrayStack(int maxStack) {
        this.maxStack = maxStack;
        //初始化当前数组的容量
        stack = new int[maxStack];
    }

    //判断是否已经满栈
    public boolean isFull(){
        return this.top == this.maxStack - 1;
    }

    //判断当前栈是否为空栈
    public boolean isEmpty(){
        return this.top == -1;
    }

    //压栈
    public void push(int val){
        //是否已经栈满
        if (isFull()){
            throw new RuntimeException("此栈已满!");
        }

        top++;
        stack[top] = val;
    }

    //出栈,弹栈
    public int pop(){
        //如果栈中是空
        if (isEmpty()){
            throw new RuntimeException("空栈,未找到数据!");
        }

        int value = stack[top];
        top--;

        return value;
    }

    //查看栈中所有元素
    public void list(){
        //是否是空栈
        if (isEmpty()){
            throw new RuntimeException("空栈,未找到数据!");
        }

        for (int i = 0; i < stack.length; i++) {
            System.out.printf("stack[%d] = %d\n",i,stack[i]);
        }
    }

    //栈中元素存在的个数
    public int length(){
        return this.top + 1;
    }

    //判断是否是一个运算符 + - * /
    public boolean isOper(char v){
        return v == '+' ||v == '-' ||v == '*' ||v == '/';
    }

    //判断运算符优先级 使用数字表示优先级大小,数字越大的优先级越大
    public int priority(int oper){
        if (oper == '*' ||oper == '/'){
            return 1;
        }else if (oper == '+' ||oper == '-'){
            return 0;
        }else {
            return -1;
        }
    }

    //获取栈顶数据
    public int peek(){
        return this.stack[top];
    }

    //获取栈的容量
    public int stackLength(){
        return this.stack.length;
    }

    //计算两个数进行运算后的结果
    //比如: 2 + 3
    //弹栈的时候,先3出来,后2出来,所以此时num1 = 3,num2 = 2
    public int calculate(int num1,int num2,int oper){
        //计算结果
        int result = 0;
        switch (oper){
            case '+':
                result = num1 + num2;
                break;
            case '-':
                result = num2 - num1;
                break;
            case '*':
                result = num1 * num2;
                break;
            case '/':
                result = num2 / num1;
                break;
            default:
                break;
        }
        return result;
    }
}

测试代码

package stackPractice;

public class stackPracticeTest02 {
    public static void main(String[] args) {
        String str = "4+3+2*3-1";
        //数字栈
        ArrayStack numStack = new ArrayStack(10);
        //运算符栈
        ArrayStack symbolStack = new ArrayStack(10);

        //定义变量参数
        int temp1 = 0;
        int temp2 = 0;
        int symbolChar = 0;
        int result = 0;
        //获取字符串长度
        int length = str.length();
        //字符的拼接,比如:33
        String values = "";
        for (int i = 0; i < length; i++) {
             //获取每一个字符
            char c = str.charAt(i);
            //是否是一个运算符
            if (symbolStack.isOper(c)){
                //如果不是一个空运算符栈
                if (!symbolStack.isEmpty()){
                    //比较运算符的优先级
                    if (symbolStack.priority(c) <= symbolStack.priority(symbolStack.peek())){
                        //去运算符栈中获取栈顶的运算符
                        //去数字栈中获取两个数字
                        temp1 = numStack.pop();
                        temp2 = numStack.pop();
                        symbolChar = symbolStack.pop();

                        result = numStack.calculate(temp1, temp2, symbolChar);

                        //把运算结果再次放入数字栈中
                        numStack.push(result);

                        //把当前的运算符压入运算符栈中
                        symbolStack.push(c);
                    }else {
                        symbolStack.push(c);
                    }
                }else {
                    //如果是空运算符栈,将运算符直接压栈
                    symbolStack.push(c);
                }
            }else {
                //比如:33+44
                values += c;

                if (i == length - 1){//如果是最后一个,直接压栈
                    numStack.push(Integer.parseInt(values));
                }else {
                    //如果不是最后一个,将下一个取出
                    char data = str.substring(i + 1,i + 2).charAt(0);
                    //上面等价于:char data = str.charAt(i + 1);
                    //判断当前的是不是一个运算符
                    if (symbolStack.isOper(data)){
                        //如果是运算符,就认为当前的数字结束了,进行压栈
                        numStack.push(Integer.parseInt(values));
                        values = "";
                    }
                }
            }
        }

        while (true){
            if (symbolStack.isEmpty()){
                break;
            }

            temp1 = numStack.pop();
            temp2 = numStack.pop();
            symbolChar = symbolStack.pop();
            result = numStack.calculate(temp1, temp2, symbolChar);
            numStack.push(result);
        }

        int res = numStack.pop();
        System.out.println("结果是:" + res);
    }
}

3.链表

链表是一种物理存储单元上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的
特点:

  1. 链表是以结点形式存储的,是链式存储
  2. 每个节点包含data区域和next区域
  3. 各个结点并不是连续存储的

3.1 单链表

需求:根据带有头部的链表,实现商品增删改查,并且也可以针对商品已编号进行排序,完成排行榜

商品节点

package linkedPractice;

public class GoodsNode {
    public int id;
    public String name;
    public double price;
    public GoodsNode next;

    public GoodsNode(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

链表类

package linkedPractice;

public class DLLinkedList {
    private GoodsNode node = new GoodsNode(0,"",0.0);

    //往链表尾部中添加结点
    public void add(GoodsNode goodsNode){
        GoodsNode temp = node;
        while (true){
            if (temp.next == null){
                break;
            }
            temp = temp.next;
        }
        temp.next = goodsNode;
    }

    //按照商品编号id值进行添加,从小到大的顺序添加
    public void addOrder(GoodsNode goodsNode){
        GoodsNode temp = node;
        //标记id相等的情况
        boolean flg = false;

        while (true){
            if (temp.next == null){
                break;
            }
            if (temp.next.id > goodsNode.id){
                break;
            }else if(temp.next.id == goodsNode.id){
                flg = true;
                break;
            }

            temp = temp.next;
        }

        if (flg){
            System.out.println("已经存在了该商品,不能添加重复元素!");
        }else {
            goodsNode.next = temp.next;
            temp.next = goodsNode;
        }
    }

    //修改结点
    //1.先找到链表中目标结点 2.根据新的数据修改
    public void updateNode(GoodsNode goodsNode){
        //如果链表空
        if (node.next == null){
            System.out.println("链表为空...");
            return;
        }

        GoodsNode temp = node.next;

        //标识符,表示找到了结点
        boolean flg = false;
        while (true){
            //直到链表中的最后一个结点未找到
            if (temp == null){
                break;
            }

            //找到结点,循环结束
            if (temp.id == goodsNode.id){
                flg = true;
                break;
            }
            //拿下一个元素,继续往下找
            temp = temp.next;
        }

        if (flg){
            //真正的修改结点
            temp.name = goodsNode.name;
            temp.price = goodsNode.price;
        }else {
            System.out.println("在整个链表中未找到目标结点....");
        }
    }

    //结点删除功能
    //条件:根据结点的编号删除
    public void delNode(int id){
        GoodsNode temp = node;

        //标识符,表示找到了结点
        boolean flg = false;
        while (true){
            if (temp.next == null){
                break;
            }
            if (temp.next.id == id){
                flg = true;
                break;
            }
            //拿下一个元素,继续往下找
            temp = temp.next;
        }

        if (flg == true){
            temp.next = temp.next.next;
        }else {
            System.out.println("未找到删除的结点...");
        }
    }

    //查询功能
    //查看链表中每一个结点元素
    public void list(){
        if (node.next == null){
            System.out.println("空链表");
            return;
        }

        GoodsNode temp = node.next;
        while (true){
            if (temp == null){
                break;
            }
            System.out.println(temp);

            temp = temp.next;
        }
    }
}

测试类

package linkedPractice;

public class LinkedTest {
    public static void main(String[] args) {

        DLLinkedList linkedList1 = new DLLinkedList();
        DLLinkedList linkedList2 = new DLLinkedList();

        GoodsNode goodsNode1 = new GoodsNode(1,"运动鞋",599.50);
        GoodsNode goodsNode2 = new GoodsNode(2,"上衣",399.50);
        GoodsNode goodsNode3 = new GoodsNode(3,"短裤",50.50);
        GoodsNode goodsNode4 = new GoodsNode(4,"拖鞋",150.50);

        linkedList1.add(goodsNode1);
        linkedList1.add(goodsNode2);
        linkedList1.add(goodsNode3);
        linkedList1.add(goodsNode4);

        linkedList1.list();

        //这里会有问题:在上面链表链接后,在这里只要将第一个链上,后面2,3,4自动链上的
        System.out.println("===================================");

        linkedList2.addOrder(goodsNode3);
        linkedList2.addOrder(goodsNode1);
        linkedList2.addOrder(goodsNode4);
        linkedList2.addOrder(goodsNode2);

        linkedList2.list();

        System.out.println("===================================");
        linkedList2.updateNode(new GoodsNode(2,"太阳帽",200));
        linkedList2.list();

        System.out.println("===================================");
        linkedList2.delNode(3);
        linkedList2.list();
    }
}

运行结果:

在这里插入图片描述

3.2 单链表面试题

需求:计算单链表中存在的结点个数,不统计头结点

在上面的链表类中添加方法

//计算单链表中存在的结点个数【不统计头结点】
    public int getLength(){
        if (node.next == null){
            System.out.println("空链表");
            return 0;
        }

        GoodsNode temp = node.next;
        int length = 0;
        while (temp != null){
            //结点个数
            length++;
            temp = temp.next;
        }
        return length;
    }

测试代码

System.out.println(linkedList1.getLength());

3.3 双链表

  1. 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱
  2. 从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点

书本结点

package linkedPractice;

public class BookNode {
    public int id;
    public String name;
    public double price;

    //结点下一个结点,直接后继
    public BookNode next;
    //上一个结点,直接前驱
    public BookNode pre;

    public BookNode(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

双向链表

package linkedPractice;

public class DualLinkedList {
    //创建空的头结点
    private BookNode head = new BookNode(0,"",0.0);

    //向尾部添加新的结点
    public void addLast(BookNode newNode){
        BookNode temp = head;

        while (true){
            //找到最后一个结点
            if (temp.next == null){
                break;
            }
            temp = temp.next;
        }

        //新的结点作为上一个尾部结点的直接后继
        //尾部结点作为新的结点的直接前驱
        temp.next = newNode;
        newNode.pre = temp;
        newNode.next = null;
    }

    //修改结点
    //条件:双向链表中的每一个结点的id和修改的id对比,如果对比成功,则进行修改该结点
    //如果没有对比成功,双向链表中未找到目标结点
    public void updateNode(BookNode node){
        //是否是空链表
        if (head.next == null){
            System.out.println("空链表...");
            return;
        }

        BookNode temp = head.next;

        boolean flg = false;
        while (true){
            if (temp == null){
                break;
            }
            if (temp.id == node.id){
                flg = true;
                break;
            }
            temp = temp.next;
        }

        if (flg){
            temp.name = node.name;
            temp.price = node.price;
        }else {
            System.out.println("未找到需要修改的结点...");
        }
    }

    //双向链表的删除
    //条件:根据id编写进行删除结点
    public void delNode(int id){
        //是否是空链表
        if (head.next == null){
            System.out.println("空链表...");
            return;
        }

        BookNode temp = head.next;

        boolean flg = false;
        while (true){
            if (temp == null){
                break;
            }
            if (temp.id == id){
                flg = true;
                break;
            }
            temp = temp.next;
        }

        if (flg){
           temp.pre.next = temp.next;
           if (temp.next != null){
               temp.next.pre = temp.pre;
           }
        }else {
            System.out.println("未找到需要删除的结点...");
        }
    }
}

测试类:

package linkedPractice;

public class LinkedTest02 {
    public static void main(String[] args) {
        DualLinkedList dualLinkedList = new DualLinkedList();
        
        //创建结点
        BookNode bookNode1 = new BookNode(1,"红楼梦",88.0);
        BookNode bookNode2 = new BookNode(2,"西游记",88.0);
        BookNode bookNode3 = new BookNode(3,"水浒传",88.0);
        BookNode bookNode4 = new BookNode(4,"三国演义",88.0);
        
        //添加操作
        dualLinkedList.addLast(bookNode1);
        dualLinkedList.addLast(bookNode2);
        dualLinkedList.addLast(bookNode3);
        dualLinkedList.addLast(bookNode4);

        System.out.println("============================");
        //删除操作
        dualLinkedList.delNode(2);

        System.out.println("============================");
        //修改操作
        dualLinkedList.updateNode(new BookNode(3,"小飞侠",520.00));
    }
}

运行结果
debug查看运行结果:
添加效果:
在这里插入图片描述
删除效果:
在这里插入图片描述
修改效果:
在这里插入图片描述

3.4 单向环形链表(约瑟夫问题)

约瑟夫问题【约瑟夫环】:
设编号为1,2,…,n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到 m 的那个人出列,它的下一位又从1开始报数,数到m 的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列
在这里插入图片描述

结点类

package linkedPractice;

public class Boy {
    //结点编号
    private int no;
    //指向下一个结点
    private Boy next;

    public Boy(int no) {
        this.no = no;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public Boy getNext() {
        return next;
    }

    public void setNext(Boy next) {
        this.next = next;
    }
}

环形链表

package linkedPractice;

public class CircleSingleLinkedList {

    //首结点
    private Boy first = new Boy(-1);

    //构建环形链表
    public void addBoy(int nums){
        if(nums < 1){
            System.out.println("数据不正确");
            return;
        }

        //辅助结点
        Boy temp = null;

        for (int i = 1; i <= nums; i++) {
            Boy boy = new Boy(i);

            //判断是否是第一个孩子,如果是第一个需要自己与自己构成环形
            if (i == 1){
                first = boy;
                first.setNext(first);
                temp = first;
            }else{
                temp.setNext(boy);
                boy.setNext(first);
                temp = boy;
            }

        }
    }

    //查看环形链表中的结点
    public void showBoy(){
        if (first == null){
            System.out.println("链表为空...");
            return;
        }

        Boy boy = first;
        while (true){
            System.out.printf("小孩的编号%d\n",boy.getNo());
            if (boy.getNext() == first){
                break;
            }
            boy = boy.getNext();
        }
    }

    /**
     * 约瑟夫问题
     * @param startNo 第几个孩子开始数数
     * @param countNum 数几次
     * @param nums 环形链表中一共有几个孩子
     */
    public void countBoy(int startNo,int countNum,int nums){
        if (first == null || startNo < 1 || startNo > nums){
            System.out.println("参数输入有错误...");
            return;
        }

        //定义辅助指针,指向的是环形单链表中的最后一个结点
        Boy helper = first;
        while (true){
            if (helper.getNext() == first){
                break;
            }
            helper = helper.getNext();
        }

        //寻找起始位置,把first定义为起始位置
        for (int j = 0; j < startNo - 1; j++) {
            first = first.getNext();
            helper = helper.getNext();
        }

        //当孩子进行报数时,数到m的孩子进行出列
        //让first和helper移动m - 1次即可,找到了出列的孩子
        while (true){
            if (helper == first){
                break;
            }
            for (int j = 0; j < countNum - 1; j++) {
                first  = first.getNext();
                helper = helper.getNext();
            }

            System.out.printf("孩子%d 出列\n",first.getNo());
            first = first.getNext();
            helper.setNext(first);
        }

        System.out.printf("最后出圈的孩子编号%d\n",first.getNo());
    }
}

测试类

package linkedPractice;

public class LinkedTest03 {
    public static void main(String[] args) {
        //定义环
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();

        //定义五个孩子
        circleSingleLinkedList.addBoy(5);

        //输出展示
        circleSingleLinkedList.showBoy();

        //约瑟夫问题模拟
        circleSingleLinkedList.countBoy(1,2,5);
    }
}

运行结果

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值