数据结构与算法

数据结构与算法

数据结构包括:线性结构【数组、队列、链表、栈】+非线性结构

稀疏数组

应用场景

棋盘、地图 ===》二维数组的压缩

  • 举例

demo

package com.summer.datastructure;

/**
 * 稀疏数组
 */
public class SparseArray {
    public static void main(String[] args) {
        //创建一个原始的二维数组  8*8
        // 0表示没有棋子 1:表示黑子  2:表示白子
        int[][] chessArray = new int[8][8];
        chessArray[1][2] = 1;
        chessArray[2][3] = 2;
        chessArray[4][5] = 2;
        System.out.println("原始的二维数组");
        for (int[] row :chessArray){
            for (int i : row) {
                System.out.print(i+" ");
            }
            System.out.println();
        }
        //将二位数组 转 稀疏数组的思想
        //1、将二维数组遍历得到 非零 数 的个数
        int sum = 0;
        for (int[] row : chessArray){
            for (int i : row) {
                if (i != 0){
                    sum++;
                }
            }
        }
        //2、创建对应的稀疏数组
        int[][] sparseArray = new int[sum+1][3];
        sparseArray[0][0] = 8;
        sparseArray[0][1] = 8;
        sparseArray[0][2] = sum;
        //遍历二维数组 将非零值 存放到稀疏数组
        for(int i= 0;i<8;i++){
            if (sum == 0){
                break;
            }
            for (int j = 0; j < 8; j++) {
                if (sum == 0){
                    break;
                }
                if (chessArray[i][j] != 0){
                        sparseArray[sum][0]=i;
                        sparseArray[sum][1]=j;
                        sparseArray[sum][2]=chessArray[i][j];
                        sum--;
                }
            }
        }
        //转换后的稀疏数组
        System.out.println("转换为稀疏数组:");
        for (int[] row:sparseArray){
            for (int i:row){
                System.out.print(i+" ");
            }
            System.out.println();
        }

        //将稀疏数组转换为二维数组
        int[][] chessArrayCopy = new int[sparseArray[0][0]][sparseArray[0][1]];
        for (int i = 1; i<=sparseArray[0][2];i++){
                chessArrayCopy[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];
        }
        System.out.println("稀疏数组恢复为二维数组:");
        for (int[] ints : chessArrayCopy) {
            for (int anInt : ints) {
                System.out.print(anInt+" ");
            }
            System.out.println();
        }
    }
}

结果:

原始的二维数组
0 0 0 0 0 0 0 0 
0 0 1 0 0 0 0 0 
0 0 0 2 0 0 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 2 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 
转换为稀疏数组:
8 8 3 
4 5 2 
2 3 2 
1 2 1 
稀疏数组恢复为二维数组:
0 0 0 0 0 0 0 0 
0 0 1 0 0 0 0 0 
0 0 0 2 0 0 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 2 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 

练习

要求:【主要是IO操作】
1、在前面的基础上,将稀疏数组保存到磁盘上,比如map.data
2、恢复原来的数组时,读取map.data进行恢复【模拟存棋盘读棋盘】

队列

介绍

  • 应用场景 ------》银行排队叫号系统

  • 队列是一个有序列表,可以用数组或是链表来实现。

  • 遵循先入先出的原则,即:先存入队列的数据,要先取出。后存入的要后取出。

数组模拟队列

  • 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图所示,其中maxSize是该队列的最大容量
  • 因为队列的输出、输入是分别从前后端来处理的,因此需要两个变量front及rear分别记录队列的前后端的下标,front会随着数据输出而改变,rear则会随着数据输入而改变。

  • 加入队列addQueue
    • 将尾指针往后移:rear+1,当front==rear【队列空的】
    • 若尾指针rear小于队列的最大下标maxSize-1,则数据存入rear所指的数组元素中,否则无法存入数据。rear == maxSize -1 【队列满了】

demo

package com.summer.datastructure.queue;

import java.util.Scanner;

public class ArrayQueueDemo {
    public static void main(String[] args) {
        ArrayQueue arrayQueue = new ArrayQueue(3);
        Scanner scanner = new Scanner(System.in);
        char key = ' ';//用户输入
        boolean loop = true;
        while(loop){
            System.out.println("s(show):查看队列");
            System.out.println("e(exit):退出程序");
            System.out.println("a(add):添加数据到队列");
            System.out.println("g(get):从队列中取数据");
            System.out.println("h(head):查看队列头的数据");
            key = scanner.next().charAt(0);
            switch (key) {
                case 's':
                    arrayQueue.show();
                    break;
                case 'e':
                    scanner.close();//关闭
                    loop=false;
                    break;
                case 'a':
                    System.out.println("请输入一个数字");
                    int n = scanner.nextInt();
                    arrayQueue.addQueue(n);
                    break;
                case 'g':
                    try {
                        System.out.println("取出的数据是:"+arrayQueue.getQueue());
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        System.out.println("队列头数据是:"+arrayQueue.headQueue());
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                default:
                    System.out.println("输入无效信息");
                    break;
            }
        }
        System.out.println("程序退出");
    }
}

//使用数组模拟队列---编写一个ArrayQueue的类
class ArrayQueue{
    private int maxSize;//数组最大容量
    private int front;//队列头
    private int rear;//队列尾
    private int[] arr;//该数组用于存放数据,模拟队列

    //创建队列的构造器
    public ArrayQueue(int arrMaxSize){
        maxSize = arrMaxSize;
        arr = new int[maxSize];
        front = -1;//指向队列头部,分析出front是指向队列头的前位置
        rear = -1;//指向队列的尾,指向队列尾的数据【即就是队列的最后一个数据】
    }

    public boolean isFull(){
        return  rear == maxSize-1;
    }

    public boolean isEmpty(){
        return front == rear;
    }

    //入队列
    public void addQueue(int n){
        if (isFull()){
            System.out.println("队列已满,不能加入数据");
            return;
        }
        arr[++rear] = n;
    }

    //出队列
    public int getQueue(){
        if (isEmpty()){
            System.out.println("队列为空,无数据");
            throw  new  RuntimeException("队列空,不能取数据");
        }
        return arr[++front];//由于队列头指向数据前一个位置,所以要++ 取数据
    }

    //显示队列所有数据
    public void show(){
        if (isEmpty()){
            System.out.println("队列为空,没有数据");
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.printf("arr[%d] = %d",i,arr[i]);
            System.out.println();
        }
    }

    //显示队列的头的数是谁,注意不是取数据
    public int headQueue(){
        if (isEmpty()){
            System.out.println("队列为空,无数据");
            throw  new RuntimeException("队列空,不能取数据");
        }
        return arr[front+1];
    }

}

结果:

s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
队列为空,没有数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
10
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
20
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0] = 10
arr[1] = 20
arr[2] = 0
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0] = 10
arr[1] = 20
arr[2] = 30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
40
队列已满,不能加入数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
h
队列头数据是:10
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:10
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
h
队列头数据是:20
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0] = 10
arr[1] = 20
arr[2] = 30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:20
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
队列为空,无数据
队列空,不能取数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
199
队列已满,不能加入数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据

发现问题

  • 目前数组使用一次就不能用了,没有达到复用的效果。
  • 改进:使用算法将这个数组改成一个环形的数组,核心是用取模 %

数组环形队列

更新如下[环形队列数组]

1、front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素【front的初始默认为0】

2、rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为约定【rear初始值为0】

3、当队列满时,(rear + 1)% maxSize == front【满】

4、当队列空时,rear == front【空】

5、当我们这样分析,队列红有效的数据个数 (rear + maxSize -front) % maxSize

demo

package com.summer.datastructure.queue;

import java.util.Scanner;

public class CircleArrayQueueDemo {
    public static void main(String[] args) {
        System.out.println("测试数组模拟环形队列");
        CircleArray circleArray = new CircleArray(4);//其队列的有效数据最大是3
        Scanner scanner = new Scanner(System.in);
        char key = ' ';//用户输入
        boolean loop = true;
        while(loop){
            System.out.println("s(show):查看队列");
            System.out.println("e(exit):退出程序");
            System.out.println("a(add):添加数据到队列");
            System.out.println("g(get):从队列中取数据");
            System.out.println("h(head):查看队列头的数据");
            key = scanner.next().charAt(0);
            switch (key) {
                case 's':
                    circleArray.show();
                    break;
                case 'e':
                    scanner.close();//关闭
                    loop=false;
                    break;
                case 'a':
                    System.out.println("请输入一个数字");
                    int n = scanner.nextInt();
                    circleArray.addQueue(n);
                    break;
                case 'g':
                    try {
                        System.out.println("取出的数据是:"+circleArray.getQueue());
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        System.out.println("队列头数据是:"+circleArray.headQueue());
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                default:
                    System.out.println("输入无效信息");
                    break;
            }
        }
        System.out.println("程序退出");
     }
    }

class CircleArray{
    private int maxSize;//数组最大容量
    //front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素【front的初始默认为0】
    private int front;
    //rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为约定【rear初始值为0】
    private int rear;
    private int[] arr;//该数组用于存放数据,模拟队列

    //创建队列的构造器
    public CircleArray(int arrMaxSize){
        maxSize = arrMaxSize;
        arr = new int[maxSize];
//        front = 0;
//        rear =  0;
    }

    public boolean isFull(){
        return  (rear + 1) % maxSize == front;
    }

    public boolean isEmpty(){
        return front == rear;
    }

    //入队列
    public void addQueue(int n){
        if (isFull()){
            System.out.println("队列已满,不能加入数据");
            return;
        }
        arr[rear] = n;
        //将rear后移,这里必须考虑取模
        rear = ++rear % maxSize;
    }

    //出队列
    public int getQueue(){
        if (isEmpty()){
            System.out.println("队列为空,无数据");
            throw  new  RuntimeException("队列空,不能取数据");
        }
        //这里分析出front是指向队列的第一个元素
        //1、先把front对应的值保存到一个临时变量
        //2、将front后移
        //3、将临时保存的变量返回
        int val = arr[front];
        front =++front % maxSize;
        return val;
    }

    //显示队列所有数据
    public void show(){
        if (isEmpty()){
            System.out.println("队列为空,没有数据");
            return;
        }
        //思路:从front开始遍历,遍历多少个元素
        for (int i = front; i <front + size(); i++) {
            System.out.printf("arr[%d]=%d\n",i%maxSize,arr[i%maxSize]);
        }
    }
    //求出当前队列有效数据的个数
    private int size(){
        //rear = 2
        //front = 1
        //maxSize = 3
        //size = 1
        return (rear+maxSize-front)%maxSize;
    }

    //显示队列的头的数是谁,注意不是取数据
    public int headQueue(){
        if (isEmpty()){
            System.out.println("队列为空,无数据");
            throw  new RuntimeException("队列空,不能取数据");
        }
        return arr[front];
    }

}

结果:

测试数组模拟环形队列
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
队列为空,没有数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
10
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0]=10
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
20
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0]=10
arr[1]=20
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0]=10
arr[1]=20
arr[2]=30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
40
队列已满,不能加入数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:10
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[1]=20
arr[2]=30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
40
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[1]=20
arr[2]=30
arr[3]=40
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
50
队列已满,不能加入数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:20
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:30
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:40
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
队列为空,没有数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
队列为空,无数据
队列空,不能取数据
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
50
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
60
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
70
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[0]=50
arr[1]=60
arr[2]=70
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:50
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[1]=60
arr[2]=70
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
90
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[1]=60
arr[2]=70
arr[3]=90
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
g
取出的数据是:60
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
a
请输入一个数字
100
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
s
arr[2]=70
arr[3]=90
arr[0]=100
s(show):查看队列
e(exit):退出程序
a(add):添加数据到队列
g(get):从队列中取数据
h(head):查看队列头的数据
e
程序退出

链表

介绍[Linked List]

  • 链表是有序列表,但他在内存中是存储如下

小结:

1、链表是以节点的方式来存储

2、每个节点包含data域 和 next域:指向下一个节点

3、如图:发现链表的各个节点不一定是连续存放的

4、链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定

单向链表的创建/添加

方法一:直接添加到链表尾部

思路

添加(创建):

1、先创建一个head头节点,作用就是表示单链表的头

2、后面我们每添加一个节点,就直接加入到链表的最后

遍历:

1、通过一个临时变量帮助遍历整个链表

demo
package com.summer.datastructure.linkedlist;

/**
 * 单链表
 */
public class SingleLinkedListDemo {
    public static void main(String[] args) {
        //测试:先创建几个节点
        HeroNode h1 = new HeroNode(1,"宋江","及时雨");
        HeroNode h2 = new HeroNode(2,"卢俊义","玉麒麟");
        HeroNode h3 = new HeroNode(3,"吴用","智多星");
        HeroNode h4 = new HeroNode(4,"林冲","豹子头");

        SingleLinkedList singleLinkedList = new SingleLinkedList();
        singleLinkedList.add(h1);
        singleLinkedList.add(h4);
        singleLinkedList.add(h3);
        singleLinkedList.add(h2);
        singleLinkedList.show();
    }
}

/**
 * 定义SingleLinkedList 管理我们的英雄
 */
class SingleLinkedList{
    //初始化一个头节点  头节点不要动 不存放具体的数据
    private HeroNode head = new HeroNode(0,"","");

    //添加节点到单向链表
    //思路:当不考虑编号的数据时
    //1、找到当前链表的最后节点
    //2、将最后这个节点的next 指向新的节点
    public void add(HeroNode heroNode){
        //因为head节点不能变,因此我们需要一个辅助变量temp
        HeroNode temp = head;
        //遍历链表,找到最后
        while (true){
            //找到链表的最后
            if(temp.next == null){
                break;
            }
            temp = temp.next;
        }
        //当退出while循环时 temp就指向了链表的最后
        temp.next = heroNode;//将最后的节点指向新的节点
    }

    public void show(){
        //判断链表是否为空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        //因为头节点不能动 因此需要一个临时变量来遍历
        HeroNode temp = head.next;
        while(temp != null){
            System.out.println(temp);
            temp = temp.next;
        }
        System.out.println("显示结束");
    }
}
/**
 * 定义一个HeroNode  每个HeroNode 对象就是一个节点
 */
class HeroNode{
    public int no;
    public String name;
    public String nickName;
    public HeroNode next;//指向下一个节点

    public HeroNode(int hNo,String hName,String hNickName){
        this.no = hNo;
        this.name = hName;
        this.nickName = hNickName;
    }

    @Override
    public String toString() {
        return "HeroNode [no = " +no+" , name = "+name+" , nickName = "+nickName+"]";
    }
}

结果:

HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 4 , name = 林冲 , nickName = 豹子头]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
显示结束

方法二:根据排名添加到指定位置

[添加时,如果有这个排名,则添加失败并给出提示]

思路

需要按照编号的顺序添加:

1、首先找到新添加的节点的位置,是通过辅助变量找到的[指针],通过遍历搞定

2、新的节点.next = temp.next

3、将temp.next = 新节点

demo
package com.summer.datastructure.linkedlist;

/**
 * 单链表
 */
public class SingleLinkedListDemo {
    public static void main(String[] args) {
        //测试:先创建几个节点
        HeroNode h1 = new HeroNode(1,"宋江","及时雨");
        HeroNode h2 = new HeroNode(2,"卢俊义","玉麒麟");
        HeroNode h3 = new HeroNode(3,"吴用","智多星");
        HeroNode h4 = new HeroNode(4,"林冲","豹子头");

        SingleLinkedList singleLinkedList = new SingleLinkedList();
//        singleLinkedList.add(h1);
//        singleLinkedList.add(h4);
//        singleLinkedList.add(h3);
//        singleLinkedList.add(h2);
//        singleLinkedList.show();

        singleLinkedList.addByOrder(h1);
        singleLinkedList.addByOrder(h4);
        singleLinkedList.addByOrder(h3);
        singleLinkedList.addByOrder(h2);
        singleLinkedList.show();
    }
}

/**
 * 定义SingleLinkedList 管理我们的英雄
 */
class SingleLinkedList{
    //初始化一个头节点  头节点不要动 不存放具体的数据
    private HeroNode head = new HeroNode(0,"","");

    //添加节点到单向链表
    //思路:当不考虑编号的数据时
    //1、找到当前链表的最后节点
    //2、将最后这个节点的next 指向新的节点
    public void add(HeroNode heroNode){
        //因为head节点不能变,因此我们需要一个辅助变量temp
        HeroNode temp = head;
        //遍历链表,找到最后
        while (true){
            //找到链表的最后
            if(temp.next == null){
                break;
            }
            temp = temp.next;
        }
        //当退出while循环时 temp就指向了链表的最后
        temp.next = heroNode;//将最后的节点指向新的节点
    }

    //第二种方式在添加英雄时,根据排名将英雄插入到指定位置
    //[添加时,如果有这个排名,则添加失败并给出提示]
    public void  addByOrder(HeroNode heroNode){
        //因为头节点不能动,因此我们需要一个辅助变量(指针)帮助我们找到添加的位置
        //因为单链表,所以我们找的temp是位于添加位置的前一个节点,否则插入不了
        HeroNode temp = head;
        boolean flag = false;//添加的编号是否存在 默认为false
        while (true){
            if (temp.next == null){
                temp.next = heroNode;
                break;
            }
            if(temp.next.no > heroNode.no){
                heroNode.next = temp.next;
                temp.next = heroNode;
                break;
            }else if (temp.next.no == heroNode.no){
                System.out.println("ERROR---编号已经存在了");
                break;
            }
            temp = temp.next;
        }
    }

    public void show(){
        //判断链表是否为空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        //因为头节点不能动 因此需要一个临时变量来遍历
        HeroNode temp = head.next;
        while(temp != null){
            System.out.println(temp);
            temp = temp.next;
        }
        System.out.println("显示结束");
    }
}
/**
 * 定义一个HeroNode  每个HeroNode 对象就是一个节点
 */
class HeroNode{
    public int no;
    public String name;
    public String nickName;
    public HeroNode next;//指向下一个节点

    public HeroNode(int hNo,String hName,String hNickName){
        this.no = hNo;
        this.name = hName;
        this.nickName = hNickName;
    }

    @Override
    public String toString() {
        return "HeroNode [no = " +no+" , name = "+name+" , nickName = "+nickName+"]";
    }


}

结果:

HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
HeroNode [no = 4 , name = 林冲 , nickName = 豹子头]
显示结束
public class SingleLinkedListDemo {
    public static void main(String[] args) {
        //测试:先创建几个节点
        HeroNode h1 = new HeroNode(1,"宋江","及时雨");
        HeroNode h2 = new HeroNode(2,"卢俊义","玉麒麟");
        HeroNode h3 = new HeroNode(3,"吴用","智多星");
        HeroNode h4 = new HeroNode(4,"林冲","豹子头");

        SingleLinkedList singleLinkedList = new SingleLinkedList();
//        singleLinkedList.add(h1);
//        singleLinkedList.add(h4);
//        singleLinkedList.add(h3);
//        singleLinkedList.add(h2);
//        singleLinkedList.show();

        singleLinkedList.addByOrder(h1);
        singleLinkedList.addByOrder(h1);
        singleLinkedList.addByOrder(h3);
        singleLinkedList.addByOrder(h2);
        singleLinkedList.show();
    }
}

ERROR---编号已经存在了
HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
显示结束

单向链表的更新

demo

主要看update方法

package com.summer.datastructure.linkedlist;

/**
 * 单链表
 */
public class SingleLinkedListDemo {
    public static void main(String[] args) {
        //测试:先创建几个节点
        HeroNode h1 = new HeroNode(1,"宋江","及时雨");
        HeroNode h2 = new HeroNode(2,"卢俊义","玉麒麟");
        HeroNode h3 = new HeroNode(3,"吴用","智多星");
        HeroNode h4 = new HeroNode(4,"林冲","豹子头");

        SingleLinkedList singleLinkedList = new SingleLinkedList();
//        singleLinkedList.add(h1);
//        singleLinkedList.add(h4);
//        singleLinkedList.add(h3);
//        singleLinkedList.add(h2);
//        singleLinkedList.show();

        singleLinkedList.addByOrder(h1);
        singleLinkedList.addByOrder(h1);
        singleLinkedList.addByOrder(h3);
        singleLinkedList.addByOrder(h3);
        singleLinkedList.addByOrder(h2);
        singleLinkedList.show();

        System.out.println("------update------");
        singleLinkedList.update(new HeroNode(1,"宋江11","及时雨11"));
        singleLinkedList.show();

        System.out.println("------update------");
        singleLinkedList.update(new HeroNode(6,"Summer","come on"));
        singleLinkedList.show();
    }
}

/**
 * 定义SingleLinkedList 管理我们的英雄
 */
class SingleLinkedList{
    //初始化一个头节点  头节点不要动 不存放具体的数据
    private HeroNode head = new HeroNode(0,"","");

    //添加节点到单向链表
    //思路:当不考虑编号的数据时
    //1、找到当前链表的最后节点
    //2、将最后这个节点的next 指向新的节点
    public void add(HeroNode heroNode){
        //因为head节点不能变,因此我们需要一个辅助变量temp
        HeroNode temp = head;
        //遍历链表,找到最后
        while (true){
            //找到链表的最后
            if(temp.next == null){
                break;
            }
            temp = temp.next;
        }
        //当退出while循环时 temp就指向了链表的最后
        temp.next = heroNode;//将最后的节点指向新的节点
    }

    //第二种方式在添加英雄时,根据排名将英雄插入到指定位置
    //[添加时,如果有这个排名,则添加失败并给出提示]
    public void  addByOrder(HeroNode heroNode){
        //因为头节点不能动,因此我们需要一个辅助变量(指针)帮助我们找到添加的位置
        //因为单链表,所以我们找的temp是位于添加位置的前一个节点,否则插入不了
        HeroNode temp = head;
        boolean flag = false;//添加的编号是否存在 默认为false
        while (true){
            if (temp.next == null){
                temp.next = heroNode;
                break;
            }
            if(temp.next.no > heroNode.no){
                heroNode.next = temp.next;
                temp.next = heroNode;
                break;
            }else if (temp.next.no == heroNode.no){
                System.out.println("ERROR---编号已经存在了");
                break;
            }
            temp = temp.next;
        }
    }
//修改节点的信息,根据no编号来修改,即no编号不能改
    //说明:
    //根据newHeroNode 的 no  来修改即可
    public void update(HeroNode newHeroNode){
        boolean flag = false;
        if(head.next == null){
            System.out.println("链表为空");
            return;
        }
        //先定义一个辅助变量
        HeroNode temp = head;
        while(temp.next != null){
            if (temp.no == newHeroNode.no){
                temp.nickName = newHeroNode.nickName;
                temp.name = newHeroNode.name;
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (!flag){
            System.out.println("链表中没有对应no的数据");
        }
    }



    public void show(){
        //判断链表是否为空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        //因为头节点不能动 因此需要一个临时变量来遍历
        HeroNode temp = head.next;
        while(temp != null){
            System.out.println(temp);
            temp = temp.next;
        }
        System.out.println("显示结束");
    }
}
/**
 * 定义一个HeroNode  每个HeroNode 对象就是一个节点
 */
class HeroNode{
    public int no;
    public String name;
    public String nickName;
    public HeroNode next;//指向下一个节点

    public HeroNode(int hNo,String hName,String hNickName){
        this.no = hNo;
        this.name = hName;
        this.nickName = hNickName;
    }

    @Override
    public String toString() {
        return "HeroNode [no = " +no+" , name = "+name+" , nickName = "+nickName+"]";
    }
}
ERROR---编号已经存在了
ERROR---编号已经存在了
HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
显示结束
------update------
HeroNode [no = 1 , name = 宋江11 , nickName = 及时雨11]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
显示结束
------update------
链表中没有对应no的数据
HeroNode [no = 1 , name = 宋江11 , nickName = 及时雨11]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
显示结束

Process finished with exit code 0

单向链表的删除

demo

主要看del方法

package com.summer.datastructure.linkedlist;

/**
 * 单链表
 */
public class SingleLinkedListDemo {
    public static void main(String[] args) {
        //测试:先创建几个节点
        HeroNode h1 = new HeroNode(1,"宋江","及时雨");
        HeroNode h2 = new HeroNode(2,"卢俊义","玉麒麟");
        HeroNode h3 = new HeroNode(3,"吴用","智多星");
        HeroNode h4 = new HeroNode(4,"林冲","豹子头");

        SingleLinkedList singleLinkedList = new SingleLinkedList();
//        singleLinkedList.add(h1);
//        singleLinkedList.add(h4);
//        singleLinkedList.add(h3);
//        singleLinkedList.add(h2);
//        singleLinkedList.show();

        singleLinkedList.addByOrder(h1);
        singleLinkedList.addByOrder(h1);
        singleLinkedList.addByOrder(h3);
        singleLinkedList.addByOrder(h3);
        singleLinkedList.addByOrder(h2);
        singleLinkedList.show();

        System.out.println("------update------");
        singleLinkedList.update(new HeroNode(1,"宋江11","及时雨11"));
        singleLinkedList.show();

        System.out.println("------update------");
        singleLinkedList.update(new HeroNode(6,"Summer","come on"));
        singleLinkedList.show();

        System.out.println("------delete------");
        singleLinkedList.del(1);
        singleLinkedList.show();
        singleLinkedList.del(3);
        singleLinkedList.show();
        singleLinkedList.del(3);
        singleLinkedList.show();
        singleLinkedList.del(2);
        singleLinkedList.show();
        singleLinkedList.del(2);
        singleLinkedList.show();
    }
}

/**
 * 定义SingleLinkedList 管理我们的英雄
 */
class SingleLinkedList{
    //初始化一个头节点  头节点不要动 不存放具体的数据
    private HeroNode head = new HeroNode(0,"","");

    //添加节点到单向链表
    //思路:当不考虑编号的数据时
    //1、找到当前链表的最后节点
    //2、将最后这个节点的next 指向新的节点
    public void add(HeroNode heroNode){
        //因为head节点不能变,因此我们需要一个辅助变量temp
        HeroNode temp = head;
        //遍历链表,找到最后
        while (true){
            //找到链表的最后
            if(temp.next == null){
                break;
            }
            temp = temp.next;
        }
        //当退出while循环时 temp就指向了链表的最后
        temp.next = heroNode;//将最后的节点指向新的节点
    }

    //第二种方式在添加英雄时,根据排名将英雄插入到指定位置
    //[添加时,如果有这个排名,则添加失败并给出提示]
    public void  addByOrder(HeroNode heroNode){
        //因为头节点不能动,因此我们需要一个辅助变量(指针)帮助我们找到添加的位置
        //因为单链表,所以我们找的temp是位于添加位置的前一个节点,否则插入不了
        HeroNode temp = head;
        boolean flag = false;//添加的编号是否存在 默认为false
        while (true){
            if (temp.next == null){
                temp.next = heroNode;
                break;
            }
            if(temp.next.no > heroNode.no){
                heroNode.next = temp.next;
                temp.next = heroNode;
                break;
            }else if (temp.next.no == heroNode.no){
                System.out.println("ERROR---编号已经存在了");
                break;
            }
            temp = temp.next;
        }
    }
//修改节点的信息,根据no编号来修改,即no编号不能改
    //说明:
    //根据newHeroNode 的 no  来修改即可
    public void update(HeroNode newHeroNode){
        boolean flag = false;
        if(head.next == null){
            System.out.println("链表为空");
            return;
        }
        //先定义一个辅助变量
        HeroNode temp = head;
        while(temp.next != null){
            if (temp.no == newHeroNode.no){
                temp.nickName = newHeroNode.nickName;
                temp.name = newHeroNode.name;
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (!flag){
            System.out.println("链表中没有对应no的数据");
        }
    }

    //删除节点
    //思路
    //1、head节点不能动 因此我们需要一个temp辅助节点找到待删除节点的前一个节点
    //2、说明我们在比较时,是temp.next.no 和 需要删除的节点的no比较
    public void del(int no){
        HeroNode temp = head;
        while (temp.next != null){
            if (temp.next.no == no){
                temp.next = temp.next.next;
                return;
            }
            temp = temp.next;
        }
    }


    public void show(){
        //判断链表是否为空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        //因为头节点不能动 因此需要一个临时变量来遍历
        HeroNode temp = head.next;
        while(temp != null){
            System.out.println(temp);
            temp = temp.next;
        }
        System.out.println("显示结束");
    }
}
/**
 * 定义一个HeroNode  每个HeroNode 对象就是一个节点
 */
class HeroNode{
    public int no;
    public String name;
    public String nickName;
    public HeroNode next;//指向下一个节点

    public HeroNode(int hNo,String hName,String hNickName){
        this.no = hNo;
        this.name = hName;
        this.nickName = hNickName;
    }

    @Override
    public String toString() {
        return "HeroNode [no = " +no+" , name = "+name+" , nickName = "+nickName+"]";
    }
}
------delete------
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
显示结束
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
显示结束
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
显示结束
链表为空
链表为空

单链表面试题(新浪\百度\腾讯)

单链表的常见面试题有如下:

1、求单链表中有效节点的个数【遍历一遍链表即可】

    //遍历单链表
public  int linkedLength () {
        HeroNode temp = head;
        int i = 0;
        while (temp.next != null){
             i++;
             temp = temp.next;
        }
        return i;
    }

2、查找单链表中倒数第K个节点【新浪】

//思路:先求出链表长度 然后倒数改为正着数遍历
    public HeroNode index(int k){
        HeroNode temp = head;
        if (k>linkedLength() || k<=0){
            throw new RuntimeException("参数输入异常");
        }
        int i = linkedLength() + 1 - k;
        for (int i1 = 0; i1 < i; i1++) {
            temp = temp.next;
        }
        return temp;
    }

3、单链表的反转【腾讯】

//思路
//1、先定义一个节点reverseHead = new HeroNode()
//2、从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表的reverseHead的最前端
//3、原来的链表的head.next = reverseHead.next

4、从尾到头打印单链表【百度:要求方式1:反向遍历 方式2:Stack栈】

5、合并两个有序的单链表,合并之后的链表依然有序【课后练习】

单向链表的缺点

  • 单项链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
  • 单项链表不能自我删除,需要靠辅助节点,而双向链表则可以自我删除,所以单向链表删除时节点,总是找到temp,temp是待删除节点的前一个节点来删除的。

双向链表

双向链表增删查改

demo

package com.summer.datastructure.linkedlist;

public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        System.out.println("双向链表的测试");
        HeroNode2 h1 = new HeroNode2(1,"宋江","及时雨");
        HeroNode2 h2 = new HeroNode2(2,"卢俊义","玉麒麟");
        HeroNode2 h3 = new HeroNode2(3,"吴用","智多星");
        HeroNode2 h4 = new HeroNode2(4,"林冲","豹子头");

        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        doubleLinkedList.add(h1);
        doubleLinkedList.add(h2);
        doubleLinkedList.add(h3);
        doubleLinkedList.add(h4);

        doubleLinkedList.list();
        //修改
        HeroNode2 newHeroNode2 = new HeroNode2(4,"公孙胜","入云龙");
        doubleLinkedList.update(newHeroNode2);
        System.out.println("修改过后的双向链表情况");
        doubleLinkedList.list();
        doubleLinkedList.del(3);
        doubleLinkedList.del(4);
        System.out.println("删除后双向链表情况");
        doubleLinkedList.list();
    }
}
//创建一个双向链表的类
class DoubleLinkedList{
    //先初始化一个头节点,头节点不要动,不存放具体的数据
    private HeroNode2 head = new HeroNode2(0,"","");

    //返回头节点
    public HeroNode2 getHead(){
        return head;
    }

    //遍历双向链表的方法
    public void list(){
        if (head.next == null){
            System.out.println("双向链表为空");
            return;
        }
        HeroNode2 temp = head;
        while (temp.next != null){
            System.out.println(temp.next);
            temp = temp.next;
        }
    }

    //添加
    public void add(HeroNode2 heroNode2){
        HeroNode2 temp = head;
        while (temp.next != null){
            temp = temp.next;
        }
        //星辰
        temp.next = heroNode2;
        heroNode2.pre = temp;
    }
    //修改【可以看到双向链表的节点内容修改跟单向链表一样】
    //只是节点的类型改成了HeroNode2
    public void update(HeroNode2 heroNode2){
        HeroNode2 temp = head;
        while(temp != null){
            if (temp.no == heroNode2.no){
                temp.nickName = heroNode2.nickName;
                temp.name = heroNode2.name;
                return;
            }
            temp = temp.next;
        }
        System.out.println("链表中没有对应no的数据");
    }
    //从双向链表中删除节点
    public void del(int id){
        HeroNode2 temp = head;
        while (temp != null){
            if (temp.no == id){
                temp.pre.next = temp.next;
                //如果是最后一个节点 temp.next.pre会报空指针
                if (temp.next!= null){
                    temp.next.pre = temp.pre;
                }
                return;
            }
            temp = temp.next;
        }
        System.out.println("没有对应id的节点");
    }

}

/**
 * 定义一个HeroNode  每个HeroNode 对象就是一个节点
 */
class HeroNode2{
    public int no;
    public String name;
    public String nickName;
    public HeroNode2 next;//指向下一个节点 默认为null
    public HeroNode2 pre;//指向前一个节点 默认为null

    public HeroNode2(int hNo,String hName,String hNickName){
        this.no = hNo;
        this.name = hName;
        this.nickName = hNickName;
    }

    @Override
    public String toString() {
        return "HeroNode [no = " +no+" , name = "+name+" , nickName = "+nickName+"]";
    }
}

结果:

双向链表的测试
HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
HeroNode [no = 4 , name = 林冲 , nickName = 豹子头]
修改过后的双向链表情况
HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]
HeroNode [no = 3 , name = 吴用 , nickName = 智多星]
HeroNode [no = 4 , name = 公孙胜 , nickName = 入云龙]
删除后双向链表情况
HeroNode [no = 1 , name = 宋江 , nickName = 及时雨]
HeroNode [no = 2 , name = 卢俊义 , nickName = 玉麒麟]

Process finished with exit code 0

单向环形链表的应用场景

【约瑟夫、约瑟夫环】问题

设定编号为1,2,.....n 的 n 个人围坐一圈,约定编号为 k (1<=k<=n) 的人从1开始报数,数到 m 的那个人出列,它的下一个人又开始从1报数,数到 m 的那个人 又出列,一次类推,直到所有人出列为止,由此产生一个出队编号的序列。

提示:

用一个不带头节点的循环链表来处理约瑟夫问题,先构成一个有 n 个节点的单循环链表,然后由 k 节点起从1开始计数,计到 m 时,对应节点从链表中删除,然后被删除的节点的下一个节点又从1开始计数,直到最后一个节点从链表中删除算法结束。

步骤一:

构建一个单向的环形链表思路:

1、先创建第一个节点,让first指向该节点,并形成环形

2、后面我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可

遍历环形链表:

1、先让一个辅助指针指向first节点

2、然后通过一个while循环遍历该环形链表即可 current.next == first 遍历结束

步骤二:

根据用户输入,生成一个小孩出圈的顺序

1、需要创建一个辅助指针helper,事先应该指向环形链表的最后这个节点

2、小孩报数前先让first和helper移动到k位置处

3、当小孩报数时,让first和helper同时移动m-1次(因为小孩自己也要报数)

4、这时就可以将first指向的小孩节点出圈

​ helper.next = first.next

​ first = first.next

​ 原来first指向的节点就没有任何引用,就会被垃圾回收机制回收

demo

package com.summer.datastructure.linkedlist;

public class Josepfu {
    public static void main(String[] args) {
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);
        circleSingleLinkedList.list();
        circleSingleLinkedList.countBoy(1,2,5);
    }
}

//创建一个环形的单向链表
class CircleSingleLinkedList{
    //创建一个first节点;
    private Boy first;
    //添加小孩节点
    public void addBoy(int nums){
        if (nums<1){
            System.out.println("nums 的值不正确");
            return;
        }
        Boy curBoy = null;//辅助指针,帮助构建环形链表
        for (int i = 1; i <= nums; i++) {
            //根据编号创建Boy节点
            Boy boy = new Boy(i);
            //如果是第一个小孩
            if (i==1){
                first = boy;
                first.setNext(first);//构成环
                curBoy = first;
            }else {
                curBoy.setNext(boy);
                curBoy = boy;
                boy.setNext(first);
            }
        }
    }

    //遍历单向环形链表
    public void list(){
        if (first == null){
            System.out.println("单向环形链表为空");
            return;
        }
        Boy temp = first;
        while (temp != null){
            System.out.println("编号:---"+temp.getNo());
            if (temp.getNext() == first){
                break;
            }
            temp = temp.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;
        }

        //先移动到要报数的位置
        for (int i = 0; i < startNo-1; i++) {
            first = first.getNext();
        }

        Boy helper = first;
        //事先让helper指向first后面
        while(helper != null){
            if (helper.getNext() == first){//说明helper指向了最后的节点
                break;
            }
            helper = helper.getNext();
        }

        //循环操作 让小孩出圈 直到圈中只有一个节点
        while (true){
            //只有一个节点
            if (first == helper){
                System.out.println("编号【"+first.getNo()+"】小孩 出圈");
                System.out.println("----game over----");
                break;
            }
            //移动m-1
            for (int i = 0; i < countNum-1; i++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            System.out.printf("编号【%d】小孩 出圈\n",first.getNo());
            //出圈
            first = first.getNext();
            helper.setNext(first);
        }
    }
}

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;
    }
}

结果:

编号:---1
编号:---2
编号:---3
编号:---4
编号:---5
编号【2】小孩 出圈
编号【4】小孩 出圈
编号【1】小孩 出圈
编号【5】小孩 出圈
编号【3】小孩 出圈
----game over----

栈stack

介绍

  • 栈是一个先入后出的有序列表
  • 栈是限制线性表中元素的插入和删除 只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
  • 根据栈的定义可知,最先放入栈中的元素在栈底,最后放入元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。

栈的应用场景

  • 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
  • 处理递归调用:和子程序的调用类似,只是除了存储下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
  • 表达式的转换 (中缀表达式转后缀表达式) 与求值(实际解决)
  • 二叉树的遍历
  • 图形的深度优先(depth-first)搜索法。

数组模拟栈

数组模拟栈的思路分析图

1、使用数组来模拟栈

2、定义一个top来表示栈顶,初始化为 -1

3、入栈的操作,当有数据加入到栈时,top++,stack[top] = data;

4、出栈的操作, int value = stack[top]; top-- ;return value;

demo

package com.summer.datastructure.linkedlist;

public class Josepfu {
    public static void main(String[] args) {
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);
        circleSingleLinkedList.list();
        circleSingleLinkedList.countBoy(1,2,5);
    }
}

//创建一个环形的单向链表
class CircleSingleLinkedList{
    //创建一个first节点;
    private Boy first;
    //添加小孩节点
    public void addBoy(int nums){
        if (nums<1){
            System.out.println("nums 的值不正确");
            return;
        }
        Boy curBoy = null;//辅助指针,帮助构建环形链表
        for (int i = 1; i <= nums; i++) {
            //根据编号创建Boy节点
            Boy boy = new Boy(i);
            //如果是第一个小孩
            if (i==1){
                first = boy;
                first.setNext(first);//构成环
                curBoy = first;
            }else {
                curBoy.setNext(boy);
                curBoy = boy;
                boy.setNext(first);
            }
        }
    }

    //遍历单向环形链表
    public void list(){
        if (first == null){
            System.out.println("单向环形链表为空");
            return;
        }
        Boy temp = first;
        while (temp != null){
            System.out.println("编号:---"+temp.getNo());
            if (temp.getNext() == first){
                break;
            }
            temp = temp.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;
        }

        //先移动到要报数的位置
        for (int i = 0; i < startNo-1; i++) {
            first = first.getNext();
        }

        Boy helper = first;
        //事先让helper指向first后面
        while(helper != null){
            if (helper.getNext() == first){//说明helper指向了最后的节点
                break;
            }
            helper = helper.getNext();
        }

        //循环操作 让小孩出圈 直到圈中只有一个节点
        while (true){
            //只有一个节点
            if (first == helper){
                System.out.println("编号【"+first.getNo()+"】小孩 出圈");
                System.out.println("----game over----");
                break;
            }
            //移动m-1
            for (int i = 0; i < countNum-1; i++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            System.out.printf("编号【%d】小孩 出圈\n",first.getNo());
            //出圈
            first = first.getNext();
            helper.setNext(first);
        }
    }
}

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;
    }
}

结果

请出入你的选择
show
stack---index【2】data【30】
stack---index【1】data【20】
stack---index【0】data【10】
show:表示显示栈
exit:程序退出
push:表示入栈
pop:表示出栈
请出入你的选择
pop
出栈
30
show:表示显示栈
exit:程序退出
push:表示入栈
pop:表示出栈
请出入你的选择
pop
出栈
20
show:表示显示栈
exit:程序退出
push:表示入栈
pop:表示出栈
请出入你的选择
pop
出栈
10
show:表示显示栈
exit:程序退出
push:表示入栈
pop:表示出栈
请出入你的选择
pop
出栈
栈空
Exception in thread "main" java.lang.RuntimeException: 栈空,没有数据
	at com.summer.datastructure.stack.ArrayStack.pop(ArrayStackDemo.java:76)
	at com.summer.datastructure.stack.ArrayStackDemo.main(ArrayStackDemo.java:29)

Process finished with exit code 1

栈实现综合计算器

使用栈完成表达式的计算思路
1、通过一个index 值 [索引],用来遍历我们的表达式
2、如果我们发现是一个数字,就直接入数栈
3、如果发现扫描到的是一个符号,就分如下情况
3.1 如果发现当前的符号栈为空,就直接入栈
3.2 如果符号栈有操作符,就进行比较,如果当前的操作符的优先 级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,进行运算,将得到的结果,入数栈,然后将当前的操作符如符号栈,如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
4、当表达式扫描完成,就顺序的从数栈和符号栈中pop出相应的数和符号,并运算
5、最后在数栈只有一个数字,就是表达式的结果

demo

package com.summer.datastructure.stack;

public class Calculator {
    public static void main(String[] args) {
//        String expression = "3+2*6-2";
        String expression = "30+20*6-2";
        //创建两个栈  一个数栈 一个符号栈
        ArrayStack2 numStack = new ArrayStack2(10);
        ArrayStack2 operStack = new ArrayStack2(10);
        //定义需要的相关变量
        int index = 0;//用于扫描
        int num1  = 0;
        int num2  = 0;
        int oper  = 0;
        int res = 0;
        char ch = ' ';//将每次扫描得到的char保存到ch
        String keepNum = "";//用于拼接多位数的
        while (true){
            if (index == expression.length()){
                while (!operStack.isEmpty()){
                    num1 = numStack.pop();
                    num2 = numStack.pop();
                    int cal = numStack.cal(num1, num2, operStack.pop());
                    numStack.push(cal);
             }
                break;
            }
            //依次得到expression的每一个字符
            ch = expression.substring(index,index+1).charAt(0);
            if (operStack.isOper(ch)){//如果是运算符
                if (operStack.isEmpty()){
                    operStack.push(ch);
                }else {
                    int curOperPriority = operStack.priority(ch);
                    if (curOperPriority >= operStack.priority(operStack.peek())){
                        operStack.push(ch);
                    }else {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        res =  numStack.cal(num1,num2,operStack.pop());
                        numStack.push(res);
                        operStack.push(ch);
                    }
                }
            }else {
//                numStack.push(ch -48);
                //分析思路
                //1、当初处理多位数时,不能发现是一个数就立即入栈,因为他可能是多位数
                //2、在处理数,需要向expression的表达式的index 后再看一位,如果是数就进行扫描,如果是符号才入栈
                //3、因此我们需要定义一个变量字符串 用于拼接
                //处理多位数
                keepNum +=ch;
                //判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,则入数栈

                //如果ch已经是expression最后一位 直接入栈
                if (index == expression.length() -1){
                    numStack.push(Integer.parseInt(keepNum));
                }else {
                    if (operStack.isOper(expression.substring(index+1,index+2).charAt(0))){// index 不要变只是往后看一位
                        //后一位是运算符 则入栈
                        numStack.push(Integer.parseInt(keepNum));
                        //!!!清空keepnum
                        keepNum = "";
                    }
                }
            }
            index++;
        }
        System.out.printf("%s = %d \n",expression,numStack.pop());
    }
}


//定义一个数组栈  需要扩展功能
class ArrayStack2{
    private int maxSize;//栈的大小
    private int[] stack;//数组,数组模拟栈,数据就放在该数组中
    private int top = -1;//top表示栈顶,初始化为-1

    public int getTop() {
        return top;
    }

    //构造器
    public ArrayStack2(int maxSize){
        this.maxSize = maxSize;
        stack = new int[maxSize];
    }

    public boolean isFull(){
        return top == maxSize-1;
    }

    public boolean isEmpty(){
        return top == -1;
    }

    public void push(int data){
        if (isFull()){
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top] = data;
    }


    public int pop(){
        if (isEmpty()){
            System.out.println("栈空");
            throw new RuntimeException("栈空,没有数据");//运行异常 不捕获也能抛出
        }
        int temp = stack[top];
        top--;
        return temp;
    }
    //遍历栈的时候 要从栈顶开始遍历
    public void list(){
        if (isEmpty()){
            System.out.println("栈空,没有数据");
            return;
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack---index【%d】data【%d】\n",i,stack[i]);
        }
    }
    //返回运算符的优先级,优先级是程序员确定的,优先级使用数字表示
    //数字越大,则优先级越高
    public int priority(int oper){
        if (oper == '*' || oper == '/'){
            return 1;
        }else if (oper == '+' || oper == '-'){
            return 0;
        }else {
            return -1;//假定目前的表达式只有 加减乘除
        }
    }

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

    //计算方法
    public int cal(int num1,int num2,int oper){ ;
        int result = 0;
        if (oper == '+'){
            result =num1 + num2;
        }else if (oper == '-'){
            result = num2 - num1;//注意顺序
        }else if (oper == '*'){
            result = num1 * num2;
        }else if (oper == '/'){
            result = num2 / num1;//注意顺序
        }
        return result;
    }

    public int peek(){
        return stack[top];
    }

    public void reverse(){
        int[] temp = new int[10];
        for (int i = 0; i <= top; i++) {
            temp[i] = stack[top - i];
        }
        for (int i = 0; i <= top; i++) {
            stack[i] = temp[i];
        }
    }
}

结果

30+20*6-2 = 148 

Process finished with exit code 0

前缀、中缀、后缀表达式

前缀表达式

  • 前缀表达式又称波兰表达式,前缀表达式的运算符位于操作数之前

    e.g.举例说明 (3+4)*5-6 对应的前缀表达式就是 - * + 3 4 5 6

前缀表达式的计算机求值过程

从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素和次顶元素),并将结果入栈;重复上述过程直到表达式的最左端,最后运算得出的值即为表达式的结果

例如:(3+4)*5-6 对应的前缀表达式就是 - * + 3 4 5 6 针对前缀表达式求值步骤如下:

1、从右至左扫描,将6 5 4 3压入堆栈

2、遇到 + 运算符,因此弹出3 4 3+4=7 7入栈

3、接下来* 7*5=35 35入栈

4、接下来- 35-6=29 即最终结果为29

中缀表达式

  • 中缀表达式就是常见的运算表达式,如(3+4)*5 -6
  • 中缀表达式的求值是我们人最熟悉的,但是对于计算机来说却不好操作(前面的那个计算机demo可以看出来这个问题哦),因此,在计算结果时,往往会将中缀表达式转成其他表达式来操作(一般转成后缀表达式)

后缀表达式

  • 后缀表达式又称逆波兰表达式与前缀表达式相似,只是运算符位于操作数之后
  • e.g. 举例说明 (3+4)*5 -6 对应的后缀表达式就是 3 4 + 5 * 6 -

后缀表达式的计算机求值过程

从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素和栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。

e.g.例如 (3+4)*5 -6 对应的后缀表达式就是 3 4 + 5 * 6 - 针对后缀表达式求值步骤如下:

1、从左至右扫描,将3和4压入堆栈

2、遇到+ 3+4 = 7 7 入栈

3、遇到5入栈

4、遇到* 弹出5 和 7 5*7 = 35 35 入栈

5、遇到6 入栈

6、遇到- 弹出 6和35 用次顶元素35减去栈顶元素7 = 29 为最终结果

逆波兰计算器

demo

package com.summer.datastructure.stack;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class PolandNotation {
    public static void main(String[] args) {
        //先定义逆波兰表达式
        //(3+4)*5 -6 对应的后缀表达式就是 3 4 + 5 * 6 -
        //说明为了方便,逆波兰表达式的数字和符号使用空格 隔开
        String suffixExpression = "3 4 + 5 * 6 -";
        //思路
        //1.先将"3 4 + 5 * 6 -"放到ArrayList中
        //2.将ArrayList传递给一个方法,遍历ArrayList配合栈完成计算

        List<String> rpnList = getListString(suffixExpression);
        System.out.println(rpnList);
        System.out.println("计算的结果--------> "+calculate(rpnList));
    }

    //将一逆波兰表达式,依次将数据和运算符 放入到ArrayList中
    public static List<String> getListString(String suffixExpression){
        String[] split = suffixExpression.split(" ");
        List<String> list = new ArrayList<>();
        for (String s : split) {
            list.add(s);
        }
        return list;
        }

        public static int calculate(List<String> ls){
        //创建一个栈,只需要一个栈
            Stack<String> stack  = new Stack<>();
            //遍历list
            for (String item : ls) {
                //这里使用正则表达式来取出数
                if (item.matches("\\d+")){//匹配的时多位数
                    //直接入栈
                    stack.push(item);
                }else {
                    //pop出两个数 并运算 ,运算结果在入栈
                    int num2 = Integer.parseInt(stack.pop());
                    int num1 = Integer.parseInt(stack.pop());
                    int res = 0;
                    if (item.equals("+")){
                        res =num1+num2;
                    }else if (item.equals("*")){
                        res= num1*num2;
                    }else if (item.equals("/")){
                        res = num1/num2;
                    }else if(item.equals("-")){
                        res = num1-num2;
                    }else {
                        throw new RuntimeException("运算符有误");
                    }
                    stack.push(res+"");//把整数转换为字符串
                }
            }
            //最后留在stack中的数据时运算的结果
            return Integer.parseInt(stack.pop());
        }
}

结果

[3, 4, +, 5, *, 6, -]
计算的结果--------> 29

Process finished with exit code 0

中缀表达式转后缀表达式步骤

  • 步骤1:初始化两个栈:运算符栈s1和储存中间结果的栈s2
  • 步骤2:从左至右扫描中缀表达式;
  • 步骤3:遇到操作数时,将其压入s2
  • 步骤4:遇到运算符时,比较其与s1栈顶运算符的优先级
    • 4-1:如果s1为空,或者栈顶运算符为左括号“(”,则直接将此运算符入栈
    • 4-2:否则,若优先级比栈顶运算符的高,也将运算符压入s1
    • 4-3:否则,将s1栈顶的运算符弹出并压入到s2中,再次转到4-1与s1中新的栈顶运算符比较
  • 步骤5:遇到括号时:
    • 5-1:如果是左括号( ,直接压入s1
    • 5-2:如果是右括号) ,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  • 步骤6:重复步骤2至5,直到表达式的最右边
  • 步骤7:将s1中剩余的运算符依次弹出并压入s2
  • 步骤8:依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的逆波兰表达式

e.g. 中缀表达式: 1+ ( (2 + 3) * 4 ) -5 --》 s1: - 5 + * 4 + 3 2 1 ---》 1 2 3 + 4 * + 5 -

说明:因为s2这个栈,在整个转换过程中,没有pop操作,而且后面我们还要逆序输出,因此s2可用ArrayList代替哦,实际问题自己灵活变通。

demo --- myself

package com.summer.datastructure.stack;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

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



        //完成将一个中缀表达式转换成后缀表达式的功能
        //说明
        //1、1+ ( (2 + 3) * 4 ) -5   转成  1 2 3 + 4 * + 5  -
        //2、1+ ( (2 + 3) * 4 ) -5 转换成一个ArrayList 好扫描
        //3、将中缀表达式的list转换成后缀表达式的list
//        String express = "1+((2+3)*4)-5";
        String express = "(5-3)*5/2-(8-7)";
        List<String> list = toInfixExressionList(express);
        System.out.println(list);
        List<String> suffixExpression =  parseSuffixEpressionList(list);
        List<String> suffixExpression1 =  parseSuffixEpressionListV2(list);
        System.out.println(suffixExpression);
        System.out.println(suffixExpression1);
        System.out.println(express +" self计算的结果--------> "+calculate(suffixExpression));
        System.out.println(express +" v2计算的结果--------> "+calculate(suffixExpression1));
    }

    //即:1+ ( (2 + 3) * 4 ) -5   转成  1 2 3 + 4 * + 5  -
    //将得到的中缀表达式list转换成后缀表达式list
    public static List<String> parseSuffixEpressionList(List<String> ls){
        Stack<String> stack = new Stack<>();
        List<String> result = new ArrayList<>();
        for(int i = 0;i<ls.size();i++){
            if (isOper(ls.get(i))){
                if (stack.empty()){
                    stack.push(ls.get(i));
                }else {
                    String top = stack.pop();
                    if (top.equals("(")){
                        stack.push(top);
                        stack.push(ls.get(i));
                    }else {
                        if (rank(ls.get(i)) > rank(top)){
                            stack.push(top);
                            stack.push(ls.get(i));
                        }else {
                            result.add(top);
                            i--;
                            continue;
                        }
                    }
                }
            }else if (ls.get(i).equals("(") || ls.get(i).equals(")")){
                if (ls.get(i).equals("(")){
                    stack.push(ls.get(i));
                }else {
                    String temp = stack.pop();
                    while (!temp.equals("(")){
                        result.add(temp);
                        temp = stack.pop();
                    }
                }
            }else {
                result.add(ls.get(i));
            }
        }
        while (!stack.isEmpty()){
            result.add(stack.pop());
        }

//        for (int i = result.size()-1; i >= 0; i--) {
//            finalResult.add(result.get(i));
//        }
        return result;
    }

    //
    public static List<String> parseSuffixEpressionListV2(List<String> ls){
        Stack<String> s1 = new Stack<>();
        List<String> s2 = new ArrayList<>();
        for (String item : ls) {
            if (item.matches("\\d+")){
                s2.add(item);
            }else if (item.equals("(")){
                s1.push(item);
            }else if (item.equals(")")){
                while(!s1.peek().equals("(")){
                    s2.add(s1.pop());
                }
                s1.pop();//将一对小括号弹出
            }else {
                //这里都是运算符
                //当item<=栈顶运算符优先级  就把栈顶的运算符pop出来
                    while (!s1.isEmpty() && !s1.peek().equals("(") && Operation.getValue(item) <= Operation.getValue(s1.peek())){
                        s2.add(s1.pop());
                    }
                    s1.push(item);
            }
        }
        //将s1加入s2中
        while (!s1.isEmpty()){
           s2.add(s1.pop());
        }
        return s2;
    }

    public static boolean isOper(String s){
        if (s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")){
            return true;
        }
        return false;
    }

    public static int rank(String a){
        if (a.equals("+") || a.equals("-")){
            return 1;
        }else if (a.equals("*") || a.equals("/")){
            return 2;
        }
        throw new RuntimeException("运算符优先级未添加");
    }

    //将中缀表达式抓花城对应的List
    public static List<String> toInfixExressionList(String s){
        List<String> list = new ArrayList<>();
        int i = 0;
        String str;
        while (i< s.length()){
            if (s.charAt(i) < 48 || s.charAt(i)>57){
                list.add(s.charAt(i)+"");
                i++;
            }else {
                str = "";
                while (i<s.length() && s.charAt(i) >= 48 && s.charAt(i) <= 57){
                    str = "" + s.charAt(i);
                    i++;
                }
                list.add(str);
            }
        }
        return list;
    }

    //将一逆波兰表达式,依次将数据和运算符 放入到ArrayList中
    public static List<String> getListString(String suffixExpression){
        String[] split = suffixExpression.split(" ");
        List<String> list = new ArrayList<>();
        for (String s : split) {
            list.add(s);
        }
        return list;
        }

        public static int calculate(List<String> ls){
        //创建一个栈,只需要一个栈
            Stack<String> stack  = new Stack<>();
            //遍历list
            for (String item : ls) {
                //这里使用正则表达式来取出数
                if (item.matches("\\d+")){//匹配的时多位数
                    //直接入栈
                    stack.push(item);
                }else {
                    //pop出两个数 并运算 ,运算结果在入栈
                    int num2 = Integer.parseInt(stack.pop());
                    int num1 = Integer.parseInt(stack.pop());
                    int res = 0;
                    if (item.equals("+")){
                        res =num1+num2;
                    }else if (item.equals("*")){
                        res= num1*num2;
                    }else if (item.equals("/")){
                        res = num1/num2;
                    }else if(item.equals("-")){
                        res = num1-num2;
                    }else {
                        throw new RuntimeException("运算符有误");
                    }
                    stack.push(res+"");//把整数转换为字符串
                }
            }
            //最后留在stack中的数据时运算的结果
            return Integer.parseInt(stack.pop());
        }

}

//编写一个类可以返回一个运算符对应的优先级
class Operation{
    private static int ADD = 1;
    private static int SUB = 1;
    private static int MUL = 2;
    private static int DIV = 2;

    //写一个方法,返回对应的优先级数字
    public static int getValue(String operation){
        int result = 0;
        switch (operation){
            case "+":
                result = ADD;
                break;
            case "-":
                result = SUB;
                break;
            case "*":
                result = MUL;
                break;
            case "/":
                result = DIV;
                break;
            default:
                throw new RuntimeException("运算符找不到");
        }
        return result;
    }
}

结果:

[(, 5, -, 3, ), *, 5, /, 2, -, (, 8, -, 7, )]
[5, 3, -, 5, *, 2, /, 8, 7, -, -]
[5, 3, -, 5, *, 2, /, 8, 7, -, -]
(5-3)*5/2-(8-7) self计算的结果--------> 4
(5-3)*5/2-(8-7) v2计算的结果--------> 4
    
[1, +, (, (, 2, +, 3, ), *, 4, ), -, 5]
[1, 2, 3, +, 4, *, +, 5, -]
[1, 2, 3, +, 4, *, +, 5, -]
1+((2+3)*4)-5 self计算的结果--------> 16
1+((2+3)*4)-5 v2计算的结果--------> 16

Process finished with exit code 0

递归应用

概念

简单地说:递归就是方法自己调用自己,每次调用时传入不同的变量,递归有助于编程者解决复杂的问题,同时可以让那个代码变得简洁。

posted @ 2021-03-22 00:49  学点东西真不容易  阅读(62)  评论(0编辑  收藏  举报